我注意到在 ruby 中有两种常见的猴子修补类的方法:
像这样定义类的新成员:
class Array
def new_method
#do stuff
end
end
并在类对象上调用 class_eval:
Array.class_eval do
def new_method
#do stuff
end
end
我想知道两者之间是否有任何区别,以及使用一种方法是否比另一种方法有优势?
我注意到在 ruby 中有两种常见的猴子修补类的方法:
像这样定义类的新成员:
class Array
def new_method
#do stuff
end
end
并在类对象上调用 class_eval:
Array.class_eval do
def new_method
#do stuff
end
end
我想知道两者之间是否有任何区别,以及使用一种方法是否比另一种方法有优势?
老实说,我曾经使用第一种形式(重新开课),因为它感觉更自然,但你的问题迫使我对这个主题进行一些研究,结果如下。
重新打开课程的问题在于,如果您打算重新打开的原始课程由于某种原因目前尚未定义,它将默默地定义一个新课程。结果可能不同:
如果您不覆盖任何方法而只添加新方法并且定义了原始实现(例如,加载了最初定义类的文件),那么以后一切都会好起来的。
如果您重新定义了一些方法并且稍后加载原始方法,您的方法将被其原始版本覆盖。
最有趣的情况是当您使用标准自动加载或一些花哨的重新加载机制(如 Rails 中使用的机制)来加载/重新加载类时。其中一些解决方案依赖于引用未定义常量时调用的const_missing。在这种情况下,自动加载机制会尝试找到未定义类的定义并加载它。但是,如果您自己定义类(而您打算重新打开已经定义的类),它将不再“丢失”,并且可能根本不会加载原始类,因为不会触发自动加载机制。
另一方面,如果您使用class_eval
当前未定义类,您将立即收到通知。此外,当您在调用其class_eval
方法时引用该类时,任何自动加载机制都将有机会定位类的定义并加载它。
考虑到这一点class_eval
似乎是一种更好的方法。不过,我很乐意听到其他意见。
范围
我认为 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"