Ruby:自动将实例变量设置为方法参数?

Posted

技术标签:

【中文标题】Ruby:自动将实例变量设置为方法参数?【英文标题】:Ruby: Automatically set instance variable as method argument? 【发布时间】:2012-06-07 01:03:02 【问题描述】:

是否有计划实现 ruby​​ 行为,类似于 CoffeeScript 在方法参数列表中指定实例变量名称的特性? 喜欢

class User
  def initialize(@name, age)
    # @name is set implicitly, but @age isn't.
    # the local variable "age" will be set, just like it currently works.
  end
end

我知道这个问题:in Ruby can I automatically populate instance variables somehow in the initialize method?,但所有解决方案(包括我自己的)似乎都不符合 ruby​​ 的简单理念。

而且,这种行为会有什么缺点吗?

更新

其中一个原因是 ruby​​ 社区的 DRY(不要重复自己)哲学。我经常发现自己需要重复参数变量的名称,因为我希望将它分配给同名的实例变量。

def initialize(name)
  # not DRY
  @name = name
end

我能想到的一个缺点是,如果一个方法没有主体,它可能看起来好像什么都不做。如果您正在快速扫描,这可能看起来像是无操作。但我认为只要有时间,我们就能适应。

另一个缺点:如果您在正文中设置其他实例变量,并且您试图通过将所有赋值放在开头来使其可读,则可能需要更多的认知“能力”才能看到赋值也发生在参数列表。但我认为这并不比看到一个常量或方法调用并不得不跳转到它的定义更难。

# notice: instance var assignments are happening in 2 places! 
def initialize(@name)
  @errors = []
end

【问题讨论】:

【参考方案1】:

经过一番思考,我想知道是否可以从 ruby​​ 方法中实际获取参数名称。如果是这样,我可以使用像“iv_”这样的特殊参数前缀来指示哪些参数应该设置为实例变量。

这是可能的:How to get argument names using reflection。

是的!所以我也许可以编写一个模块来为我处理这个问题。然后我被卡住了,因为如果我调用模块的辅助方法,它不知道参数的值,因为它们是调用者的本地。啊,但是 ruby​​ 有 Binding 对象。

这是模块(仅限 ruby​​ 1.9):

module InstanceVarsFromArgsSlurper
  # arg_prefix must be a valid local variable name, and I strongly suggest
  # ending it with an underscore for readability of the slurped args.
  def self.enable_for(mod, arg_prefix)
    raise ArgumentError, "invalid prefix name" if arg_prefix =~ /[^a-z0-9_]/i
    mod.send(:include, self)
    mod.instance_variable_set(:@instance_vars_from_args_slurper_prefix, arg_prefix.to_s)
  end

  def slurp_args(binding)
    defined_prefix = self.class.instance_variable_get(:@instance_vars_from_args_slurper_prefix)
    method_name = caller[0][/`.*?'/][1..-2]
    param_names = method(method_name).parameters.map|p| p.last.to_s 
    param_names.each do |pname|
      # starts with and longer than prefix
      if pname.start_with?(defined_prefix) and (pname <=> defined_prefix) == 1
        ivar_name = pname[defined_prefix.size .. -1]
        eval "@#ivar_name = #pname", binding
      end
    end
    nil
  end
end

这是用法:

class User
  InstanceVarsFromArgsSlurper.enable_for(self, 'iv_')

  def initialize(iv_name, age)
    slurp_args(binding)  # this line does all the heavy lifting
    p [:iv_name, iv_name]
    p [:age, age]
    p [:@name, @name]
    p [:@age, @age]
  end
end

user = User.new("Methuselah", 969)
p user

输出:

[:iv_name, "Methuselah"]
[:age, 969]
[:@name, "Methuselah"]
[:@age, nil]
#<User:0x00000101089448 @name="Methuselah">

它不会让你有一个空的方法体,但它是 DRY。我确信它可以通过仅指定哪些方法应该具有这种行为(通过 alias_method 实现)来进一步增强,而不是在每个方法中调用 slurp_args - 规范必须在定义所有方法之后。

请注意,模块和辅助方法名称可能会有所改进。我只是使用了想到的第一件事。

【讨论】:

感谢您的回答。不过,这绝对是值得做的更多工作。 :) @jackquack 好吧,你真的只需要进行一次设置。然后在你的课堂上,你所要做的就是打电话给enable_forslurp_args【参考方案2】:

其实……

class User
  define_method(:initialize)  |@name| 
end

User.new(:name).instance_variable_get :@name
# => :name

适用于 1.8.7,但不适用于 1.9.3。现在,我只是在哪里了解到这...

【讨论】:

+1 表示勇敢的尝试。还没有尝试过,但如果它在 1.9.3 上不起作用,那就更新颖了。【参考方案3】:

我认为你回答了你自己的问题,它不符合 ruby​​ 的简单哲学。它将增加方法中如何处理参数的额外复杂性,并将管理变量的逻辑上移到方法参数中。我可以看到这样的论点,即它使代码的可读性降低了,但它确实让我觉得不是很冗长。

@参数必须应对的一些场景:

def initialize( first, last, @scope, @opts =  )

def search( @query, condition )

def ratchet( @*arg  ) 

所有这些场景都应该有效吗?只是initialize@*arg 在我看来特别危险。所有这些规则和排除使 Ruby 语言更加复杂。为了自动实例变量的好处,我认为这不值得。

【讨论】:

我会设想一个 splatted 实例 arg 是 *@arg。除此之外,没有一个场景看起来特别丑陋、复杂,甚至难以理解。就实例 var 逻辑而言,您已经可以将它们设置在 arg 列表中,尽管是以一种人为的方式:def run(a = (@v=1)); end。是的,这很丑陋,只有在没有传递参数时才设置 var。但它表明这种分配已经被允许。我认为它有助于保持代码干燥。我将在我的问题中添加另一个示例。 哎呀,当我说它使代码更干燥时,我的意思是我的问题中的提议,而不是我评论中的人为示例。

以上是关于Ruby:自动将实例变量设置为方法参数?的主要内容,如果未能解决你的问题,请参考以下文章

使用 ruby​​ 将多个实例变量转换为哈希的更好方法?

Ruby 实例变量何时设置?

在类方法中使用实例变量 - Ruby

Ruby on rails 辅助方法实例变量

编写Rectangle(矩形)类.该类具有double类型的私有实例变量

Ruby类实例变量和继承