将initialize()args设置为属性的快捷方式?

时间:2014-04-20 19:16:29

标签: ruby

这是一种常见的初始化模式:

def initialize(title, val, type)
  @title, @val, @type = title, val, type
end

是否有一个等同于“获取每个参数,创建同名属性,并将属性设置为参数值”的快捷方式?

我正在寻找一种无宝石的解决方案。

4 个答案:

答案 0 :(得分:4)

你将失去检查错误参数的功能,但可以这样做:

def initialize(*args)
  @title, @val, @type = args
end

但如果你反复这样做,那么你的代码就不对了。您最好重新设计API以获取命名参数:

def initialize(title:, val:, type:)
  ...
end

WhateverClass.new(title: "foo", val: "bar", type: "baz")

答案 1 :(得分:3)

您可以使用上面提到的 Struct 或动态执行以下操作:

class MyClass

  def initialize input
    input.each do |k,v|
      instance_variable_set("@#{k}", v) unless v.nil?
    end
  end

end

无论如何,如果是我的代码,我将使用Struct。

答案 2 :(得分:2)

以下是你如何做到这一点。将为initialize的每个参数创建一个实例变量和一个关联的读/写访问器,变量具有相同的名称,前面有@,每个实例变量都将赋值相关参数。

<强>代码

class MyClass
  def initialize(< arbitrary parameters >)
    self.class.params.each { |v|
      instance_variable_set("@#{v}", instance_eval("#{v}")) }

    < other code >
  end

  @params = instance_method(:initialize).parameters.map(&:last)
  @params.each { |p| instance_eval("attr_accessor :#{p}") }

  class << self
    attr_reader :params
  end  

  < other code >     
end

示例

class MyClass
  def initialize(a, b, c)
    self.class.params.each { |v|
      instance_variable_set("@#{v}", instance_eval("#{v}")) }
  end

  @params = instance_method(:initialize).parameters.map(&:last)
  @params.each { |p| instance_eval("attr_accessor :#{p}") }

  class << self
    attr_reader :params
  end  
end

MyClass.methods(false)
  #=> [:params]
MyClass.instance_methods(false)
  #=> [:a, :a=, :b, :b=, :c, :c=]

m = MyClass.new(1,2,3)
m.a #=> 1
m.b #=> 2
m.c #=> 3
m.a = 4
m.a #=> 4

<强>解释

解析类MyClass时,会为类实例变量@params分配一个数组,其元素为initialize的参数。这是可能的,因为在解析代码开始initialize时创建了方法@params = ...

方法Method#parameters用于获取initialize的参数。对于上面的例子,

instance_method(:initialize).parameters
  #=> [[:req, :a], [:req, :b], [:req, :c]]

所以

@params = instance_method(:initialize).parameters.map(&:last)
  #=> [:a, :b, :c]

然后我们创建读/写访问器:

@params.each { |p| instance_eval("attr_accessor :#{p}") }

以及@params的读取访问器,供initialize使用:

class << self
  attr_reader :params
end  

创建my_class的实例MyClass时,传递给MyClass.new的参数值将传递给initialize。然后initialize循环通过类实例变量@params并设置每个实例变量的值。在这个例子中,

MyClass.new(1,2,3)

调用initialize(a,b,c)其中

a => 1
b => 2
c => 3

我们有:

params = self.class.params
  #=> [:a, :b, :c]

params.each { |v| instance_variable_set("@#{v}", instance_eval("#{v}")) }

对于params:a)的第一个元素,这是:

instance_variable_set("@a", instance_eval(a) }

是:

instance_variable_set("@a", 1 }

导致@a被分配1

请注意@params的访问者不是必需的:

class MyClass
  def initialize(a, b, c)
    self.class.instance_variable_get(:@params).each { |v|
      instance_variable_set("@#{v}", instance_eval("#{v}")) }
  end

  @params = instance_method(:initialize).parameters.map(&:last)
  @params.each { |p| instance_eval("attr_accessor :#{p}") }
end

答案 3 :(得分:1)

我喜欢使用args哈希并将每个属性发送到实例。但是,这不会为这些属性创建访问器,因此如果需要,您需要添加这些属性。

def initialize(args)
  args.each do |method, value|
    self.send("#{method}=", value)
  end
end