Ruby:向输入参数的类添加方法

时间:2010-12-23 03:50:51

标签: ruby dynamic metaprogramming

我正在探索ruby,并且想知道将一个方法添加到对象类的理论可能性。例如,定义一个接受参数的方法,并将方法添加到该参数的类中(而不仅仅是参数对象本身)。像这样的例子:

class SomeClass
end

class AnotherClass
end

alpha = SomeClass.new
beta = AnotherClass.new

def AddHelloMethodTo param

 # This is where I'm trying to
 # add a method to the class of the parameter
 def param.class.Hello 
  "Hello"
 end

end

AddHelloMethodTo alpha
AddHelloMethodTo beta

gamma = AnotherClass.new

alpha.Hello
beta.Hello
gamma.Hello

(对不起,如果我有语法错误/错别字,我真的很新!) 请注意我不会在AddHelloMethodTo上调用gamma,但我希望定义Hello,因为我已将其添加到类中。
这可能吗?

2 个答案:

答案 0 :(得分:7)

这是你最接近的。我冒昧地将其更改为标准的Ruby编码风格,但请注意,唯一的真正的更改是add_hello_method_to_class_of的第一行:

class SomeClass; end
class AnotherClass; end

alpha = SomeClass.new
beta = AnotherClass.new

def add_hello_method_to_class_of(obj)
  obj.class.send(:define_method, :hello) do
    'Hello'
  end
end

add_hello_method_to_class_of(alpha)
add_hello_method_to_class_of(beta)

gamma = AnotherClass.new

alpha.hello
beta.hello
gamma.hello

最初,你有

def obj.class.hello

这样可行,但它不会按照您的想法行事。这会将单例方法添加到类对象本身,但您可能会认为它将添加实例方法。如果要添加实例方法,则需要使用Module#define_method,如下所示:

obj.class.define_method(:hello)

Module#define_methodprivate外,您需要使用反射来规避访问限制:

obj.class.send(:define_method, :hello)

请注意,我还将方法的名称从add_hello_method_to更改为add_hello_method_to_class_of,因为没有添加hello方法对于它的论点,它将它添加到它的论据 class

但是,如果你像这样进行猴子修补,通常认为使用mixins是好习惯,从那以后,mixin出现在对象的继承链中,这使得任何人调试该代码至少一个有机会弄清楚神秘的hello方法来自哪里:

# file: hello_extension.rb
module HelloExtension
  def hello
    'Hello'
  end
end

def add_hello_method_to_class_of(obj)
  obj.class.send(:include, HelloExtension)
end

# some other file
require 'hello_extension'

class SomeClass; end
class AnotherClass; end

alpha = SomeClass.new
beta = AnotherClass.new

add_hello_method_to_class_of(alpha)
add_hello_method_to_class_of(beta)

gamma = AnotherClass.new

alpha.hello
beta.hello
gamma.hello

现在,您可以轻松调试此代码:

gamma.class.ancestors
# => [AnotherClass, HelloExtension, Object, Kernel, BasicObject]

如果有人想知道hello方法的来源,那么就不需要花太多时间来确定名为HelloExtension的mixin可能与它有关。遵循标准的Ruby命名约定,他们甚至知道查找名为hello_extension.rb

的文件

你甚至可以这样做:

gamma.method(:hello).source_location
# => ['hello_extension.rb', 3]

答案 1 :(得分:1)

您的示例(一旦修复了一个小的语法错误)将类方法添加到类而不是实例方法。

要定义实例方法,可以使用class_eval并定义方法,就好像您正在定义类一样:

def AddHelloMethodTo param
  param.class.class_eval do
    def Hello
      "Hello"
    end
  end
end

现在,对象类的所有先前和未来实例都可以访问Hello方法。