猴子修补ruby中的类的推荐方法

时间:2012-04-26 16:40:33

标签: ruby monkeypatching

我注意到有两种常见的方法来修补ruby中的类:

在类上定义新成员,如下所示:

class Array
   def new_method
     #do stuff
   end
end

在类对象上调用class_eval:

Array.class_eval do
   def new_method
      #do stuff
   end
end

我想知道两者之间是否存在差异,使用一种方法是否有优势?

2 个答案:

答案 0 :(得分:56)

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

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

  1. 如果你没有覆盖任何方法但只添加新的方法并且定义了原始实现(例如,文件,最初定义了类的位置),以后一切都会好的。

  2. 如果您重新定义某些方法并且稍后加载原始文件,则会使用原始版本覆盖您的方法。

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

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

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

答案 1 :(得分:7)

<强>范围

我认为,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"