【问题标题】:Ruby: Add a method to the class of an input parameterRuby:向输入参数的类添加方法
【发布时间】:2010-12-23 03:50:50
【问题描述】:

我只是在探索 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

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

【问题讨论】:

    标签: ruby dynamic metaprogramming


    【解决方案1】:

    这是最接近你所拥有的。我冒昧地将其更改为标准 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_method是private,所以你需要使用反射来规避那个访问限制:

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

    请注意,我还将方法的名称从 add_hello_method_to 更改为 add_hello_method_to_class_of,因为它没有将 hello 方法添加到其参数中,它会将其添加到它的论点的类。

    但是,如果您像这样进行猴子修补,通常认为使用 mixin 是一种很好的做法,从那时起,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]
    

    【讨论】:

    • 哇,很好的解释。跟进,使用.send(...)绕过私有方法保护是否可以接受?并且还有理由使用 class.send(:define_method, :hello)... 而不是 .class.class_eval 吗?感谢您的出色回答以及对良好做法的建议。
    • @TJB:好吧,我们只是说使用Object#send 是规避访问控制的公认方法。首先是否可以规避访问控制是完全不同的事情。但是在这种情况下,无论如何,您都会犯下更大的封装违规行为,只需重写现有类的内部结构,所以一点隐私违规并不重要:-) 至于define_method,我只是更喜欢使用更结构化尽可能使用eval 的反射方法(define_method、instance_variable_set、...),并仅在不得已的情况下使用eval。
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 啊,好吧,当我尝试在alpha.Hello 运行它时失败了,我还遗漏了什么吗? pastie.org/1399724
    • 哦,对不起,这实际上是在你的类中添加了一个类方法而不是实例方法。让我编辑我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-11-02
    • 1970-01-01
    • 2012-08-06
    • 1970-01-01
    • 1970-01-01
    • 2015-04-30
    • 2017-03-25
    相关资源
    最近更新 更多