2012-04-26 61 views
25

我注意到,有猴子两种常用的方法修补类红宝石:推荐的方法来修补猴子在Ruby中类

定义在类中的新成员,像这样:

class Array 
    def new_method 
    #do stuff 
    end 
end 

,并呼吁class_eval类对象上:

Array.class_eval do 
    def new_method 
     #do stuff 
    end 
end 

我想知道如果有两个及是否有使用一种方法比其他优点有什么区别?

+0

的可能的复制[猴子修补VS类\ _eval?](http://stackoverflow.com/questions/9399358/monkey-patching-vs-class-eval) – akostadinov 2016-06-29 08:23:14

回答

54

老实说,我曾经使用第一种形式(重新开课),因为它感觉更自然,但是你的问题迫使我对这个主题进行一些研究,这里是结果。

重新打开该类的问题在于,如果由于某种原因您打算重新打开的原始类未在此时定义,它将默默定义一个新类。其结果可能会有所不同:

  1. 如果你不覆盖任何方法,而只是添加新的和原有的实现定义(如文件,其中最初定义的类被加载)以后一切都会好的。

  2. 如果您重新定义了某些方法,并且稍后加载了原件,则您的方法将被其原始版本覆盖。

  3. 最有趣的情况是,当您使用标准autoloading或一些花哨的重新加载机制(如Rails中使用的机制)来加载/重新加载类。其中一些解决方案依赖于在引用未定义常量时调用的const_missing。在这种情况下,自动加载机制试图找到未定义的类的定义并加载它。但是如果你自己定义类(当你打算重新打开已经定义好的类时),它不会再'丢失'了,并且原来可能永远不会被加载,因为自动加载机制不会被触发。

在另一方面,如果你使用class_eval你会立即如果该类此时不定义通知。另外,当你在调用class_eval方法时引用该类时,任何自动加载机制都将有机会定位类的定义并加载它。

考虑到这一点,class_eval似乎是一种更好的方法。虽然,我很乐意听到其他意见。

+0

良好的研究:) – 2012-04-26 18:40:07

+0

谷歌是一个毕竟是非常强大的工具=) – 2012-04-26 18:52:20

6

范围

一个大区别是,我认为,KL-7并没有指出是在你的新代码将被解释的范围:

如果你是(重新)打开类来操纵它,所添加的新代码将在(原始)类的范围内解释。
如果您使用Module#class_eval来操作类,那么您添加的新代码将在围绕您对#class_eval的调用的作用域中进行解释,并且不会意识到类作用域。如果有人不知道,这种行为可能会违反直觉并导致难以调试的错误。

CONSTANT = 'surrounding scope' 

# original class definition (uses class scope) 
class C 
    CONSTANT = 'class scope' 

    def fun() p CONSTANT end 
end 
C.new.fun # prints: "class scope" 


# monkey-patching with #class_eval: uses surrounding scope! 
C.class_eval do 
    def fun() p CONSTANT end 
end 
C.new.fun # prints: "surrounding scope" 


# monkey-patching by re-opening the class: uses scope of class C 
class C 
    def fun() p CONSTANT end 
end 
C.new.fun # prints: "class scope"