在Ruby中将实例变量添加到类中

时间:2008-09-30 00:44:23

标签: ruby metaprogramming

如何在运行时中将实例变量添加到已定义的类中,然后从类外部获取并设置其值?

我正在寻找一种元编程解决方案,它允许我在运行时修改类实例,而不是修改最初定义类的源代码。一些解决方案解释了如何在类定义中声明实例变量,但这不是我所要求的。

8 个答案:

答案 0 :(得分:63)

Ruby为此提供了方法instance_variable_getinstance_variable_set。 (docs

您可以创建和分配新的实例变量,如下所示:

>> foo = Object.new
=> #<Object:0x2aaaaaacc400>

>> foo.instance_variable_set(:@bar, "baz")
=> "baz"

>> foo.inspect
=> #<Object:0x2aaaaaacc400 @bar=\"baz\">

答案 1 :(得分:15)

您可以使用属性访问者:

class Array
  attr_accessor :var
end

现在您可以通过以下方式访问它:

array = []
array.var = 123
puts array.var

请注意,您也可以使用attr_readerattr_writer来定义getter或setter,也可以手动定义它们:

class Array
  attr_reader :getter_only_method
  attr_writer :setter_only_method

  # Manual definitions equivalent to using attr_reader/writer/accessor
  def var
    @var
  end

  def var=(value)
    @var = value
  end
end

如果您只想在单个实例上定义它,也可以使用单例方法:

array = []

def array.var
  @var
end

def array.var=(value)
  @var = value
end

array.var = 123
puts array.var

仅供参考,回应对此答案的评论,单例方法工作正常,以下是证明:

irb(main):001:0> class A
irb(main):002:1>   attr_accessor :b
irb(main):003:1> end
=> nil
irb(main):004:0> a = A.new
=> #<A:0x7fbb4b0efe58>
irb(main):005:0> a.b = 1
=> 1
irb(main):006:0> a.b
=> 1
irb(main):007:0> def a.setit=(value)
irb(main):008:1>   @b = value
irb(main):009:1> end
=> nil
irb(main):010:0> a.setit = 2
=> 2
irb(main):011:0> a.b
=> 2
irb(main):012:0> 

正如您所看到的,单例方法setit将设置相同的字段@b,就像使用attr_accessor定义的那样...所以单例方法是一个完全有效的方法来解决这个问题

答案 2 :(得分:15)

@ Readonly

如果您使用“class MyObject”是使用open类,那么请注意您正在重新定义initialize方法。

在Ruby中,没有重载...只有覆盖或重新定义......换句话说,任何给定方法只能有1个实例,所以如果你重新定义它,它就会被重新定义......并且initialize方法没有什么不同(即使它是Class对象使用的新方法)。

因此,永远不要重新定义现有方法而不先将其别名化......至少如果您想要访问原始定义。重新定义未知类的初始化方法可能非常危险。

无论如何,我认为我有一个更简单的解决方案,它使用实际的元类来定义单例方法:

m = MyObject.new
metaclass = class << m; self; end
metaclass.send :attr_accessor, :first, :second
m.first = "first"
m.second = "second"
puts m.first, m.second

您可以同时使用元类和开放类来实现更复杂的操作,并执行以下操作:

class MyObject
  def metaclass
    class << self
      self
    end
  end

  def define_attributes(hash)
    hash.each_pair { |key, value|
      metaclass.send :attr_accessor, key
      send "#{key}=".to_sym, value
    }
  end
end

m = MyObject.new
m.define_attributes({ :first => "first", :second => "second" })

以上基本上是通过“元类”方法公开元类,然后在define_attributes中使用它来动态定义一组具有attr_accessor的属性,然后使用哈希中的关联值调用属性setter。

使用Ruby,您可以获得创造性并以多种不同的方式做同样的事情; - )


仅供参考,如果您不知道,使用元类就像我所做的那样意味着您只对该对象的给定实例进行操作。因此,调用define_attributes只会为该特定实例定义那些属性。

示例:

m1 = MyObject.new
m2 = MyObject.new
m1.define_attributes({:a => 123, :b => 321})
m2.define_attributes({:c => "abc", :d => "zxy"})
puts m1.a, m1.b, m2.c, m2.d # this will work
m1.c = 5 # this will fail because c= is not defined on m1!
m2.a = 5 # this will fail because a= is not defined on m2!

答案 3 :(得分:2)

Mike Stone's answer已经相当全面,但我想补充一点细节。

即使在创建了某个实例后,您也可以随时修改您的课程,并获得您想要的结果。您可以在控制台中试用它:

s1 = 'string 1'
s2 = 'string 2'

class String
  attr_accessor :my_var
end

s1.my_var = 'comment #1'
s2.my_var = 'comment 2'

puts s1.my_var, s2.my_var

答案 4 :(得分:2)

其他解决方案也可以完美地工作,但是这里有一个使用define_method的例子,如果你不想使用开放类......那么它将为数组类定义“var”变量......但请注意它是等同于使用一个开放的类...好处是你可以为一个未知的类(所以任何对象的类,而不是打开一个特定的类)做... ... define_method将在一个方法内工作,而你不能打开方法中的一个类。

array = []
array.class.send(:define_method, :var) { @var }
array.class.send(:define_method, :var=) { |value| @var = value }

这是一个使用它的例子......请注意,array2,一个 DIFFERENT 数组也有方法,所以如果这不是你想要的,你可能需要我解释的单例方法在另一篇文章中。

irb(main):001:0> array = []
=> []
irb(main):002:0> array.class.send(:define_method, :var) { @var }
=> #<Proc:0x00007f289ccb62b0@(irb):2>
irb(main):003:0> array.class.send(:define_method, :var=) { |value| @var = value }
=> #<Proc:0x00007f289cc9fa88@(irb):3>
irb(main):004:0> array.var = 123
=> 123
irb(main):005:0> array.var
=> 123
irb(main):006:0> array2 = []
=> []
irb(main):007:0> array2.var = 321
=> 321
irb(main):008:0> array2.var
=> 321
irb(main):009:0> array.var
=> 123

答案 5 :(得分:0)

只读,回应你的编辑:

  

编辑:看起来我需要澄清一下   我正在寻找元编程   允许我修改的解决方案   运行时的类实例而不是   修改源代码   最初定义了这个类。一些   解决方案解释了如何申报   类中的实例变量   定义,但那不是我的意思   询问。对不起,感到困惑。

我认为你不太了解“开放课程”的概念,这意味着你可以随时开课。例如:

class A
  def hello
    print "hello "
  end
end

class A
  def world
    puts "world!"
  end
end

a = A.new
a.hello
a.world

以上是完全有效的Ruby代码,2个类定义可以分布在多个Ruby文件中。您可以使用Module对象中的“define_method”方法在类实例上定义新方法,但它等同于使用开放类。

Ruby中的“Open classes”意味着您可以在任何时间点重新定义任何类...这意味着添加新方法,重新定义现有方法或任何您想要的方法。听起来“开放式”解决方案确实是您正在寻找的......

答案 6 :(得分:0)

我前段时间为此写了一个宝石。它被称为“灵活”,不能通过rubygems获得,但直到昨天才能通过github获得。我删除了它,因为它对我没用。

你可以做到

class Foo
    include Flexible
end
f = Foo.new
f.bar = 1

用它没有任何错误。因此,您可以动态设置和获取对象中的实例变量。 如果你是interessted ...我可以再次将源代码上传到github。需要进行一些修改才能启用

f.bar?
#=> true

作为询问对象是否定义了实例变量“bar”的方法,但其他任何东西都在运行。

亲切的问候,musicmatze

答案 7 :(得分:0)

看起来所有以前的答案都假设您在编写代码时知道要调整的类的名称。嗯,这不总是正确的(至少,不适合我)。我可能正在迭代一堆我想要赋予一些变量的类(例如,保存一些元数据或其他东西)。在这种情况下,这样的事情就可以完成这项工作,

# example classes that we want to tweak
class Foo;end
class Bar;end
klasses = [Foo, Bar]

# iterating over a collection of klasses
klasses.each do |klass|
  # #class_eval gets it done
  klass.class_eval do
    attr_accessor :baz
  end
end

# it works
f = Foo.new
f.baz # => nil
f.baz = 'it works' # => "it works"
b = Bar.new
b.baz # => nil
b.baz = 'it still works' # => "it still works"