在Ruby中动态定义类方法

时间:2012-12-12 19:55:02

标签: ruby metaprogramming

在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

这似乎工作得很好,除了动态定义“scream”方法的块中使用的变量在“scream”方法的运行时绑定,而不是“make_animal”方法的运行时。

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

如何修改上述代码,以便Tiger尖叫"ROAR!"

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

2 个答案:

答案 0 :(得分:11)

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

在ruby中经常被认为是类方法的实际上是单例方法(如果你感兴趣的话,还有很多关于本征类的东西)。

def some_object.foo
end

构造创建单例方法。通常这将在一个类定义中,使用self,但你可以在任何事情上做,例如,如果你做

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

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

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

如果您仍然使用ruby 1.8,则无法使用define_singleton_method - 您需要使用单例方法是本征类的方法。

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

等同于使用define_singleton_method

答案 1 :(得分:4)

你的代码问题没有错误的绑定时刻。这是你在Class#class上定义方法。令人惊讶的是,Class是唯一的。所以你用“derp”版本覆盖“咆哮”版本。

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

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