【问题标题】:Adding an instance variable to a class in Ruby在 Ruby 中为类添加实例变量
【发布时间】:2010-09-14 03:50:32
【问题描述】:

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

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

【问题讨论】:

  • 可能还没有说清楚,但是你可以随时使用 class ClassName 重新打开课程; (这里有新东西);结尾。这确实向现有类添加了东西。整洁吧?
  • 是的,webmat 是对的...下面的所有响应都可以在您想要的任何文件中随时使用...因为一个叫做“开放课程”的东西,这意味着您可以随时修改课程希望通过 class 关键字。

标签: ruby metaprogramming


【解决方案1】:

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

【讨论】:

  • 如果您只希望它用于类的特定实例,这很有效......但我倾向于在实例上定义单例方法而不是使用这些方法
  • 迈克,我认为这是这个问题的实际目标。他在问如何“在运行时”添加一个变量,因此使用 instance_variable_set 而不是在代码中定义它。 “baz”很可能也是另一个变量。
  • 单例方法仍然可以达到同样的效果,请参阅我的答案底部的 IRB 输出。
【解决方案2】:

您可以使用属性访问器:

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 将设置与使用 attr_accessor 定义的字段相同的字段 @b...所以单例方法是解决此问题的一种完全有效的方法。

【讨论】:

  • 如果你打算使用单例方法,那么你应该作用于类变量而不是实例变量。这不会给你你正在寻找的结果。
  • 在我的回答中查看我对您的评论的回复......它就像我宣传的那样工作。
  • 它会起作用..您可以通过修改对象的单例/元类类来添加实例和类成员。
  • 感谢 Gishu :-) 我不知道为什么 Sixty4Bit 认为它不起作用。如果我不想对类本身进行更改,我会一直使用单例方法。另外,它们是一个非常酷的概念,所以我喜欢在一般情况下使用它们。
【解决方案3】:

@Readonly

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

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

上面基本上是通过“metaclass”方法暴露元类,然后在define_attributes中用attr_accessor动态定义一堆属性,然后用hash中的关联值调用属性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!

【讨论】:

  • 这不会将这些方法定义为 MyObject 对象而不是类本身吗?所以这将定义一个 m1.a, m1.b 其中 a 和 b 是仅用于该对象的单例方法。
  • @Mike 为什么我在您的第一个代码 sn-p 上收到错误 in '&lt;main&gt;': private method 'first=' called for #&lt;MyClass:0x000000021a4940&gt; (NoMethodError)
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    其他解决方案也可以完美运行,但这里有一个使用 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
    

    【讨论】:

      【解决方案6】:

      只读,响应您的编辑:

      编辑:看来我需要澄清一下 我正在寻找元编程 允许我修改的解决方案 运行时的类实例,而不是 修改源代码 最初定义的类。一些 解决方案解释了如何声明 类中的实例变量 定义,但那不是我 询问。很抱歉造成混乱。

      我想你不是很理解“公开课”的概念,也就是说你可以随时开课。例如:

      class A
        def hello
          print "hello "
        end
      end
      
      class A
        def world
          puts "world!"
        end
      end
      
      a = A.new
      a.hello
      a.world
      

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

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

      【讨论】:

        【解决方案7】:

        我前段时间为此写了一个 gem。它被称为“灵活”,不能通过 ruby​​gems 获得,但直到昨天才可以通过 github 获得。我删除了它,因为它对我没有用。

        你可以的

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

        使用它而不会出现任何错误。因此,您可以即时从对象中设置和获取实例变量。 如果您有兴趣...我可以再次将源代码上传到 github。它需要一些修改才能启用

        f.bar?
        #=> true
        

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

        亲切的问候,musicmatze

        【讨论】:

        • 这看起来很危险。至少,您将拼写错误转换为可运行的代码。我想不出有必要这样做的情况,更不用说是个好主意了。
        【解决方案8】:

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

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

        【讨论】:

          猜你喜欢
          • 2014-09-21
          • 2012-05-18
          • 2013-03-24
          • 1970-01-01
          • 2013-11-26
          • 2021-07-29
          • 2015-08-13
          • 1970-01-01
          • 2014-10-23
          相关资源
          最近更新 更多