为所有实例变量声明`attr_reader`而不列出它们

时间:2016-04-19 13:13:26

标签: ruby

在Ruby中,您可以通过在类定义中添加以下内容来创建getter / setter方法。

attr_reader :key1, :key2, :key4
attr_writer :key1, :key2, :key3

或等效

attr_accessor :key1, :key2, :key3

例如

class Foo
    attr_reader :blah, :bar, :foo, :rah, :jar

    def initialize a, b, c
        @blah = calculate_blah(a,b)
        @bar  = calculate_bar(a)
        @foo  = calculate_foo(b,c)
        @rah  = calculate_rah(a,b,c)
        @jar  = calculate_jar(a,c)
    end
    # etc etc
end

假设有很多实例变量,我想要getter方法 所有这些。

是否可以在所有实例变量上声明attr_reader而不将它们全部列出来?

这样做的好处是,您不必在initialize方法中维护两个变量列表(相同),另一个列在attr_reader。所以稍后如果你想添加另一个实例变量,你只需要在initialize方法中设置它,而不需要将它添加到attr_reader列表中。

如果attr_reader无法提供此功能,可能会召唤一些元编程来帮助吗?

这里的主要目标是了解Ruby中是否可行。 使用元编程通常会导致性能和默默无闻的成本。但这超出了这个问题的范围。

我更想知道是否可以做某事,而不是这是否正确。

4 个答案:

答案 0 :(得分:4)

这是使用单例方法的解决方案。注意create_getters是一种私有方法,因此外界并未意识到使用元编程(实现细节)。

class Foo
  def initialize a, b
    @foo = a + b
    @bar = a - b
    @jar = a + b + 1000

    create_getters
  end

  private

  def create_getters
    instance_variables.each do |v|
      define_singleton_method(v.to_s.tr('@','')) do
        instance_variable_get(v)
      end
    end
  end
end

在irb中运行:

2.2.1 :082 > x=Foo.new 100, 99
 => #<Foo:0x007fb4f3c31ce8 @foo=199, @bar=1, @jar=1199>
2.2.1 :083 > x.foo
 => 199
2.2.1 :084 > x.bar
 => 1
2.2.1 :085 > x.jar
 => 1199

警告:通过这种方式,对象实例化和getter方法调用都是SLOWER。

答案 1 :(得分:1)

尽管正如其他人所说,实际使用类似代码并不是一个好主意,但是可以编写你要求的元函数。 这是许多可能的不同解决方案之一。我们的想法是,在对象上已经定义了实例变量时,可以在生命周期中扩充一次使用的每个对象。这可能发生在初始化期间或之后。

class Foo
  def initialize
    @a = @b = @c = 33
    # you could call define_attr_readers here
  end

  def define_attr_readers
    # get the eigenclass of the current object
    klass = class << self; self; end

    symbols = instance_variables.map { |s|
      # remove the @ at the start of the symbol
      s.to_s[1..-1].to_sym
    }

    # augment the eigenclass
    klass.class_eval do
      symbols.each do |s|
        attr_reader s
      end
    end
  end
end

f = Foo.new
f.define_attr_readers
p f.a, f.b, f.c

答案 2 :(得分:1)

假设

class A
  def initialize
    @b=0
    @c=1
  end
end

然后

a = A.new

a.instance_variables.each { |iv| self.class.send(:attr_reader, iv.to_s[1..-1].to_sym) }

a.b #=> 0
a.c #=> 1

如果在initialize中定义了所有实例变量,则可以编写

class A
  def initialize
    @b=0
    @c=1
  end.tap { |instance| instance.instance_variables.each { |iv|
    self.class.send(:attr_reader, iv.to_s[1..-1].to_sym) } }
end

a = A.new
a.b #=> 0
a.c #=> 1

答案 3 :(得分:0)

实例化一次类以评估实例变量并重新定义类

class Hey 
  def initialize
    @foo, @bar, @shabaz = [1,2,3]
  end 
end

Hey.new.instance_variables.tap do |inst_vars|
  Hey.class_eval do
    attr_reader *inst_vars.map { |var| var.to_s.sub(/^@/, '').to_sym }
  end 
end

p Hey.new.foo
p Hey.new.bar
p Hey.new.shabaz