在Ruby中将一个实例variables添加到一个类中

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

我正在寻找一个元编程解决scheme,允许我在运行时修改类实例,而不是修改最初定义类的源代码。 一些解决scheme解释了如何在类定义中声明实例variables,但这不是我所问的。

您可以使用属性访问器:

class Array attr_accessor :var end 

现在你可以通过以下途径访问它

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

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

 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 

仅供参考,对于这个答案的评论,单例方法工作正常,以下是certificate:

 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> ab = 1 => 1 irb(main):006:0> ab => 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> ab => 2 irb(main):012:0> 

正如你所看到的,单例方法setit将设置与使用attr_accessor定义的字段相同的字段@b ,所以单例方法对于这个问题是非常有效的方法。

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

您可以创build并分配一个新的实例variables,如下所示:

 >> foo = Object.new => #<Object:0x2aaaaaacc400> >> foo.instance_variable_set(:@bar, "baz") => "baz" >> foo.inspect => #<Object:0x2aaaaaacc400 @bar=\"baz\"> 

@ 只读

如果您对“class MyObject”的使用是开放类的用法,那么请注意您正在重新定义初始化方法。

在Ruby中,不存在重载等重载或重定义…换句话说,只能有一个给定方法的实例,所以如果你重新定义它,重定义它,并初始化方法没有什么不同(尽pipe它是Class对象使用的新方法)。

因此,不要重新定义一个现有的方法,而不要先将其取消…至less如果你想访问原始定义。 重新定义一个未知类的初始化方法可能是相当危险的。

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

 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 

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

 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" }) 

上面基本上是通过“metaclass”方法暴露元类,然后在define_attributes中使用它来用attr_accessordynamic定义一堆属性,然后用hash中的相关值调用属性设置器。

有了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! 

迈克·斯通的回答已经相当全面,但我想补充一点细节。

您可以在任何时候修改您的课程,即使在创build了一些实例之后,也可以获得您期望的结果。 您可以在您的控制台中试用它:

 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 

其他的解决scheme也可以完美的工作,但是这里有一个使用define_method的例子,如果你不想使用开放的类,它会定义数组类的“var”variables…但是注意它是等价的使用公开课…好处是你可以做一个未知的类(所以任何对象的类,而不是打开一个特定的类),也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 

只读,作为对您编辑的回应:

编辑:它看起来像我需要澄清,我正在寻找一个元编程解决scheme,允许我在运行时修改类实例,而不是修改最初定义类的源代码。 一些解决scheme解释了如何在类定义中声明实例variables,但这不是我所问的。 对困惑感到抱歉。

我觉得你对“公开课”的概念不是很了解,这意味着你可以随时开个class。 例如:

 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中的“开放类”意味着您可以在任何时间点重新定义ANY类…这意味着添加新的方法,重新定义现有的方法或者任何您想要的东西。 这听起来像“公开课”的解决scheme真的是你在找…

我前一段时间写了一个gem。 它被称为“灵活”,并不能通过rubygems,但可以通过github直到昨天。 我删除了它,因为它对我没用。

你可以做

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

与它没有得到任何错误。 所以你可以设置和获取对象的实例variables。 如果你有兴趣…我可以再次将源代码上传到github。 它需要一些修改来启用

 f.bar? #=> true 

作为询问对象的方法是否定义了一个实例variables“bar”,但是其他的东西正在运行。

亲切的问候,音乐

它看起来像以前的所有答案都假设你知道你想要调整的类的名字是什么时候你正在编写你的代码。 那么,这并不总是如此(至less,不是我)。 我可能会迭代一堆我想要赋予一些variables的类(比如,保存一些元数据或其他东西)。 在这种情况下,这样的事情将会完成这项工作,

 # 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"