29

我注意到在 ruby​​ 中有两种常见的猴子修补类的方法:

像这样定义类的新成员:

class Array
   def new_method
     #do stuff
   end
end

并在类对象上调用 class_eval:

Array.class_eval do
   def new_method
      #do stuff
   end
end

我想知道两者之间是否有任何区别,以及使用一种方法是否比另一种方法有优势?

4

2 回答 2

59

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

重新打开课程的问题在于,如果您打算重新打开的原始课程由于某种原因目前尚未定义,它将默默地定义一个新课程。结果可能不同:

  1. 如果您不覆盖任何方法而只添加新方法并且定义了原始实现(例如,加载了最初定义类的文件),那么以后一切都会好起来的。

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

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

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

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

于 2012-04-26T18:33:46.450 回答
10

范围

我认为 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"
于 2016-01-13T02:09:43.363 回答