将方法从一个类分配给另一个类的实例

时间:2013-01-03 03:49:14

标签: ruby metaprogramming

正如标题所示,我想将一个类上定义的所有实例方法分配给另一个类。我知道我可以从ClassAClassB获取我想要复制的方法列表:

ClassA.instance_methods(false)

我认为我可以在ClassB上定义它们:

ClassA.instance_methods(false).each do |method_name|
  ClassB.method_define(method_name, [body here??])
end

有没有办法获得相应的方法体,如果有,这种方法会起作用吗?如果没有,还有办法吗?

4 个答案:

答案 0 :(得分:9)

其他人已经告诉过你了。但是为了回答你的文字问题,我们将参与UnboundMethod个对象:

class Object
  def kokot; 'kokot' end
end

o = Object.new
o.kokot
#=> kokot

3.kokot
#=> kokot

到目前为止一切顺利。现在让我们在kokot上重新定义Numeric方法:

class Numeric
  def kokot; 'pica' end
end

o.kokot
#=> kokot
3.kokot
#=> pica

但是,如果我们决定,新的kokot方法对数字很有用,但只有复数才能继续使用旧的kokot方法。我们可以这样做:

um = Object.instance_method :kokot
#=> #<UnboundMethod: Object#kokot>
Complex( 2, 3 ).kokot # gives the redefined kokot method
#=> pica
Complex.module_exec { define_method :kokot, um }
# Now we've just bound the old kokot to Complex
Complex( 2, 3 ).kokot
#=> kokot

简而言之,有一种方法可以在相关类中“复制和粘贴”方法。 目标必须是未绑定方法源的子类。方法#source_location显示文件和已定义#kokot的行:

um.source_location
#=> ["(irb)", 2]

对于内置方法,#source_location会返回nil。在Ruby 2.0中,RubyVM类具有方法#disassemble

RubyVM::InstructionSequence.disassemble( um )
#=> ( program listing goes here )

在任何情况下,Ruby字节码都不是那么漂亮。回到原始需求,甚至#define_methodUnboundMethod#bind都不能将方法绑定到不兼容的对象。这不能被像重新定义#kind_of?这样的技巧所欺骗,人们必须在本机代码中欺骗CLASS_OF()函数......

从可用的宝石中,SourcifyRubyParserSorcerer很感兴趣。 (谢谢@Casper。)使用这些,理论上可以通过#eval - 提取的方法源在不兼容的对象之间移植代码。很长的路要走,这种技术仍然无法实现可靠的方法转移,因为只要源在运行时不可用(例如自修改源),它就会失败。

答案 1 :(得分:3)

看起来你可能想要的是混合:

取自http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html

module Debug
  def whoAmI?
    "#{self.type.name} (\##{self.id}): #{self.to_s}"
  end
end
class Phonograph
  include Debug
  # ...
end
class EightTrack
  include Debug
  # ...
end
ph = Phonograph.new("West End Blues")
et = EightTrack.new("Surrealistic Pillow")
ph.whoAmI?  »   "Phonograph (#537766170): West End Blues"
et.whoAmI?  »   "EightTrack (#537765860): Surrealistic Pillow"

答案 2 :(得分:1)

在ruby 2.0中,您可以使用模块。 Matz明确地forbade this behavior from classes

但您可以使用模块中的instance_methods。

ModuleA.instance_methods(false).each do |name|
  meth = ModuleA.instance_method(name)
  ClassB.send(:define_method, name, meth)
end

define_method是一种私有方法,因此您在此处使用send

但为什么这样呢?只需包含该模块。

如果您只想将行为应用于对象,则可以从任何模块解除绑定方法并将其绑定到任何对象。

ModuleA.instance_method(:something).bind(some_object).call(args)

如果这是您想要的,请查看castinggem that adds a convenience to doing delegation这样的内容,以及仅在块的生命周期内向对象添加方法。

答案 3 :(得分:0)

在这种情况下,classB应该继承classA