根据维基百科,猴子补丁是:
一种在不改变原始源代码的情况下扩展或修改动态语言的运行时代码的方法 [...]。
同一条目中的以下语句使我感到困惑:
在 Ruby 中,monkey patch 一词被误解为对类的任何动态修改,并且通常用作在运行时动态修改任何类的同义词。
我想知道Ruby 中猴子补丁的确切含义。它是在做类似以下的事情,还是做其他事情?
class String
def foo
"foo"
end
end
根据维基百科,猴子补丁是:
一种在不改变原始源代码的情况下扩展或修改动态语言的运行时代码的方法 [...]。
同一条目中的以下语句使我感到困惑:
在 Ruby 中,monkey patch 一词被误解为对类的任何动态修改,并且通常用作在运行时动态修改任何类的同义词。
我想知道Ruby 中猴子补丁的确切含义。它是在做类似以下的事情,还是做其他事情?
class String
def foo
"foo"
end
end
我听到的关于Monkey patching/Duck-punching的最佳解释是 Patrick Ewing 在RailsConf 2007
...如果它像鸭子一样走路和像鸭子一样说话,那它就是鸭子,对吧?因此,如果这只鸭子没有给你想要的噪音,你就必须猛击那只鸭子,直到它返回你所期望的。
简短的回答是没有“确切”的含义,因为它是一个新颖的术语,不同的人使用它的方式也不同。至少可以从维基百科的文章中看出这一点。有些人坚持认为它只适用于“运行时”代码(我想是内置类),而有些人会用它来指代任何类的运行时修改。
就个人而言,我更喜欢更具包容性的定义。毕竟,如果我们只使用修改内置类的术语,我们如何指代所有其他类的运行时修改?对我来说重要的是源代码和实际运行的类之间存在差异。
在 Ruby 中,monkey patch 一词被误解为对类的任何动态修改,并且通常用作在运行时动态修改任何类的同义词。
上面的声明断言 Ruby 的使用是不正确的——但是术语会不断发展,这并不总是一件坏事。
Monkey patching 是在运行时替换类的方法(而不是像其他人描述的那样添加新方法)。
除了是一种非常不明显且难以调试的方式来更改代码之外,它还不能扩展;随着越来越多的模块开始使用猴子修补方法,更改相互影响的可能性也在增加。
这是猴子补丁:
class Float
def self.times(&block)
self.to_i.times { |i| yield(i) }
remainder = self - self.to_i
yield(remainder) if remainder > 0.0
end
end
现在我想这有时可能很有用,但想象一下如果你看到了例行公事。
def my_method(my_special_number)
sum = 0
my_special_number.times { |num| sum << some_val ** num }
sum
end
它只是在被调用时偶尔中断。对于那些关注你的人来说你已经知道为什么了,但是想象一下你不知道浮点类型有一个.times
类方法并且你自动假设它my_special_number
是一个整数。每次参数是整数、整数或浮点数时,它都可以正常工作(整数被传回,除非有浮点余数)。但是传递一个带有小数区域内任何内容的数字,它肯定会中断!
想象一下,您的 gem、Rails 插件,甚至您自己的项目中的同事,这种情况发生的频率有多高。如果有这样的一两个小方法,可能需要一些时间来查找和纠正。
如果您想知道它为什么会中断,请注意它sum
是一个整数,并且可以将浮点余数传回;此外,指数符号仅在类型相同时才有效。所以你可能认为它是固定的,因为你将麻烦的数字转换为浮点数......只是发现总和不能取浮点结果。
你是对的; 当您修改或扩展现有类而不是对其进行子类化时。
在 Python 中,monkeypatching 经常被称为尴尬的标志:“我不得不对这个类进行monkeypatch,因为......”(我在处理 Zope 时第一次遇到它,文章提到了这一点)。过去常说有必要抓住上游类并在运行时修复它,而不是游说在实际类中修复不需要的行为或在子类中修复它们。以我的经验,Ruby 的人很少谈论猴子补丁,因为它并不被认为特别糟糕甚至值得注意(因此是“打鸭子”)。显然,您必须小心更改将在其他依赖项中使用的方法的返回值,但是以 active_support 和 facets 的方式向类添加方法是非常安全的。
10 年后更新:我将最后一句修改为“相对安全”。如果其他人有相同的想法并添加具有不同实现或方法签名的相同方法,或者如果人们混淆了核心语言功能的扩展方法,则使用新方法扩展核心库类可能会导致问题。这两种情况都经常发生在 Ruby 中(尤其是关于 active_support 方法)。
这意味着您可以“动态地”修改代码。想“动态地”将方法添加到仅在“运行时”已知的特定类中吗?没问题。它很强大,是的:但可能会被滥用。“动态”这个概念可能有点太深奥而难以理解,所以我在下面准备了一个例子(我保证没有代码):
你通常如何启动汽车?很简单:你转动点火开关,汽车就启动了!
这就是法布里齐奥对可怜的迈克尔柯里昂所做的。通常,如果您想更改汽车的运行方式,则必须在汽车制造厂中进行这些更改(即在“编译”时,在 Car 类 ^** 内)。Fabrizzio 没有时间这样做:他偷偷摸摸地从引擎盖下修补汽车,偷偷地重新布线。换句话说,他重新打开了 Car 类,进行了他想要的更改,然后他就完成了:他只是修补了一辆汽车。他“动态地”做到了这一点。
当你进行猴子补丁时,你必须真正知道自己在做什么,否则结果可能会非常具有爆炸性。
“<em>法布里齐奥,你要去哪里?”</p>
“保持你的源代码接近,但你的猴子补丁更接近。”
这可能很危险。
^** 是的,我知道,动态语言。
通常它是指临时更改,使用 Ruby 开放类,通常使用低质量的代码。
对该主题的良好跟进:
http://www.infoq.com/articles/ruby-open-classes-monkeypatching