【问题标题】:Defining Class Methods Dynamically in Ruby在 Ruby 中动态定义类方法
【发布时间】:2012-12-12 19:55:02
【问题描述】:

在 Ruby 1.9.3 中,我需要创建一些类实例,每个实例具有相似的实例方法和类方法,但它们仅因几个固定参数而异。它们的类类型的区别也很重要,所以我不能简单地使用同一类的不同实例。

一个简化的例子如下所示。

module Animal
private
  def self.make_animal(name, legs, noise)
    klass = Class.new
    klass.const_set(:NUM_LEGS, legs)
    klass.class.send(:define_method, :scream) { noise.upcase + '!' }
    Animal.const_set(name, klass)
  end
  make_animal :Tiger, 4, 'roar'
  make_animal :Human, 2, 'derp'
end

这似乎工作正常,只是在动态定义“尖叫”方法的块中使用的变量是在“尖叫”方法的运行时而不是“make_animal”方法的运行时绑定的。

Animal::Human::NUM_LEGS # => 2 -- ok
Animal::Tiger::NUM_LEGS # => 4 -- ok
Animal::Human.scream # => "DERP!" -- ok
Animal::Tiger.scream # => "DERP!" -- fail!

如何修改上面的代码,让老虎尖叫"ROAR!"

[注意]我确实需要维护示例中愚蠢的 OO 结构,原因太复杂,无法在此描述。我只对学习如何在具有参数化方法实现的动态定义的类上以编程方式定义类方法感兴趣。

【问题讨论】:

    标签: ruby metaprogramming


    【解决方案1】:

    klass.class 在两种情况下都是相同的(类):所有类都是Class 的实例。结果,你定义了尖叫,然后重新定义它。

    在 ruby​​ 中通常被认为是类方法实际上是单例方法(如果您有兴趣,可以阅读很多关于 eigenclasses 等的资料)。

    def some_object.foo
    end
    

    Construct 创建单例方法。很多时候,这将在类定义中,使用 self 但你可以在任何事情上这样做,例如,如果你这样做

    x = 'dog'
    def x.bark
      "Woof"
    end
    

    然后 x.bark 将返回 woof,但不会在任何其他字符串上定义 bark。

    这里您的方法需要引用您的noise 变量,因此您需要使用define_singleton_method 来定义您的方法。

    如果您仍在使用 ruby​​ 1.8,则不能使用 define_singleton_method - 您需要使用单例方法是 eigenclass 上的方法这一事实。

    klass = Class.new
    eigenclass = class << klass; self; end
    eigenclass.send(:define_method, :scream){noise}
    

    相当于使用define_singleton_method

    【讨论】:

    • 啊,是的,define_singleton_method。忘了它:)
    • 是的,“define_singleton_method”似乎是我所缺少的。
    【解决方案2】:

    您的代码的问题不是错误的绑定时刻。这是您在Class#class 上定义方法。它是,惊喜,Class,唯一的。所以你正在用“derp”版本覆盖“roar”版本。

    相反,您应该直接在这些动态类上定义方法。这是我的看法(它对noise 使用实例变量,希望这不是问题)。

    module Animal
    private
      def self.make_animal(name, legs, noise)
        klass = Class.new
        klass.const_set(:NUM_LEGS, legs)
        klass.instance_variable_set(:@noise, noise)
    
        klass.instance_eval do |k|
          def scream
            @noise.upcase + '!'
          end
        end
    
        Animal.const_set(name, klass)
      end
    
      make_animal :Tiger, 4, 'roar'
      make_animal :Human, 2, 'derp'
    end
    
    Animal::Human::NUM_LEGS # => 2
    Animal::Tiger::NUM_LEGS # => 4
    
    Animal::Human.scream # => "DERP!"
    Animal::Tiger.scream # => "ROAR!"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-07-04
      • 2011-09-28
      • 2018-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-30
      相关资源
      最近更新 更多