假设我是猴子在类中修补方法,那么如何从覆盖方法中调用覆盖方法?即有点像super
例如
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
假设我是猴子在类中修补方法,那么如何从覆盖方法中调用覆盖方法?即有点像super
例如
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
Answers:
编辑:距离我最初写这个答案已有9年了,它值得进行一些整容手术以保持最新。
您可以在此处查看修改之前的最新版本。
您不能通过名称或关键字来调用覆盖的方法。这就是为什么应该避免猴子修补而首选继承的许多原因之一,因为显然您可以调用重写方法。
因此,如果可能的话,您应该喜欢这样的东西:
class Foo
def bar
'Hello'
end
end
class ExtendedFoo < Foo
def bar
super + ' World'
end
end
ExtendedFoo.new.bar # => 'Hello World'
如果您控制Foo对象的创建,则此方法有效。只需更改每个创建一个的地方,Foo而不是创建一个ExtendedFoo。如果您使用依赖注入设计模式,工厂方法设计模式,抽象工厂设计模式或类似的方法,则效果更好,因为在这种情况下,只需要更改即可。
如果您不控制Foo对象的创建,例如因为它们是由控件之外的框架创建的(例如轨道上的红宝石例如),那么您可以使用包装器设计模式:
require 'delegate'
class Foo
def bar
'Hello'
end
end
class WrappedFoo < DelegateClass(Foo)
def initialize(wrapped_foo)
super
end
def bar
super + ' World'
end
end
foo = Foo.new # this is not actually in your code, it comes from somewhere else
wrapped_foo = WrappedFoo.new(foo) # this is under your control
wrapped_foo.bar # => 'Hello World'
基本上,在系统的边界处,Foo对象进入您的代码,您将其包装到另一个对象中,然后在代码中的其他任何地方使用该对象而不是原始对象。
这将使用stdlib中库中的Object#DelegateClasshelper方法delegate。
Module#prepend:混入前以上两种方法需要更改系统以避免猴子补丁。本部分显示了猴子修补的首选且侵入性最小的方法,如果不应该选择更换系统,则不行。
Module#prepend被添加以或多或少地完全支持该用例。Module#prepend与进行相同的操作Module#include,除了它在类的正下方混合了mixin :
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
prepend FooExtensions
end
Foo.new.bar # => 'Hello World'
注意:我也写了一些关于Module#prepend这个问题的内容:Ruby模块前置vs派生
我已经看到有人尝试(并询问为什么它在StackOverflow上不起作用)这样的事情,即include使用mixin而不是使用prepend它:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
不幸的是,这行不通。这是一个好主意,因为它使用继承,即可以使用super。但是,Module#include将mixin插入到继承层次结构中的类之上,这意味着FooExtensions#bar将永远不会调用它(如果被调用,则它super实际上并不会引用,Foo#bar而是Object#bar不存在),因为Foo#bar它将始终被首先找到。
最大的问题是:如何在bar不实际使用实际方法的情况下继续使用该方法?答案经常像函数编程那样。我们拥有该方法作为一个实际的对象,并使用一个闭包(即一个块)来确保我们并且只有我们保留该对象:
class Foo
def bar
'Hello'
end
end
class Foo
old_bar = instance_method(:bar)
define_method(:bar) do
old_bar.bind(self).() + ' World'
end
end
Foo.new.bar # => 'Hello World'
这很干净:因为old_bar它只是一个局部变量,它将在类主体的末尾超出范围,并且即使在使用反射的情况下,也无法从任何地方访问它!而且由于Module#define_method采用了一个代码块,并且代码块在其周围的词法环境附近封闭(这就是为什么我们使用此处define_method而不是def此处),所以即使它超出了范围,它(也只有它)仍然可以访问old_bar。
简短说明:
old_bar = instance_method(:bar)
在这里,我们将bar方法包装到UnboundMethod方法对象中,并将其分配给局部变量old_bar。这意味着,bar即使覆盖了它,我们现在也可以保留它。
old_bar.bind(self)
这有点棘手。基本上,在Ruby(以及几乎所有基于单调度的OO语言)中,方法绑定到特定的接收器对象,self在Ruby中称为。换句话说:方法始终知道调用了哪个对象,知道它self是什么。但是,我们直接从类中获取了该方法,它怎么知道它self是什么?
好了,这不,这就是为什么我们需要bind我们的UnboundMethod一个对象首先,它会返回一个Method对象,就可以调用。(UnboundMethod无法调用,因为他们不知道自己不知道该怎么做self。)
那我们要做bind什么?我们bind对自己简单地做到这一点,那样它将表现得完全像原始版本一样bar!
最后,我们需要调用Method从返回的bind。在Ruby 1.9中,该(.())有一些漂亮的新语法,但是如果您使用的是1.8,则可以简单地使用call方法。那就是要.()翻译成的东西。
这是另外两个问题,其中一些概念得到了解释:
alias_method 链我们的猴子修补程序存在的问题是,当我们覆盖该方法时,该方法已消失,因此我们无法再调用它。因此,让我们备份一个副本吧!
class Foo
def bar
'Hello'
end
end
class Foo
alias_method :old_bar, :bar
def bar
old_bar + ' World'
end
end
Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'
问题在于,我们现在已经用多余的old_bar方法污染了名称空间。该方法将显示在我们的文档中,它将显示在我们的IDE中的代码完成中,并将在反射期间显示。同样,它仍然可以被调用,但是大概我们猴子修补了它,因为我们一开始不喜欢它的行为,所以我们可能不希望其他人调用它。
尽管事实上它具有一些不良的特性,但不幸的是,它已通过AciveSupport的方法得到普及Module#alias_method_chain。
如果您只需要在几个特定位置而不是整个系统中使用不同的行为,则可以使用优化将猴子补丁限制在特定范围内。我将使用Module#prepend上面的示例在此处进行演示:
class Foo
def bar
'Hello'
end
end
module ExtendedFoo
module FooExtensions
def bar
super + ' World'
end
end
refine Foo do
prepend FooExtensions
end
end
Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!
using ExtendedFoo
# Activate our Refinement
Foo.new.bar # => 'Hello World'
# There it is!
您可以在以下问题中看到使用优化的更复杂的示例:如何为特定方法启用猴子补丁?
在Ruby社区安定下来之前Module#prepend,有很多不同的想法浮出水面,您可能偶尔会在较早的讨论中看到这些想法。所有这些都包含在中Module#prepend。
一个想法是来自CLOS的方法组合器的想法。这基本上是面向方面编程的子集的非常轻量级的版本。
使用类似的语法
class Foo
def bar:before
# will always run before bar, when bar is called
end
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
您将能够“了解”该bar方法的执行。
但是,尚不清楚您是否以及如何访问bar的返回值bar:after。也许我们可以(ab)使用super关键字?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
的前组合子相当于prepend荷兰国际集团一个混合与重写方法的呼叫super在非常结束的方法的。同样,后组合子相当于prepend荷兰国际集团一个mixin具有压倒一切的方法调用super在最开始的方法。
您还可以在调用之前和之后进行操作super,可以super多次调用,并且可以检索和操纵super的返回值,这prepend比方法组合器更强大。
class Foo
def bar:before
# will always run before bar, when bar is called
end
end
# is the same as
module BarBefore
def bar
# will always run before bar, when bar is called
super
end
end
class Foo
prepend BarBefore
end
和
class Foo
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
# is the same as
class BarAfter
def bar
original_return_value = super
# will always run after bar, when bar is called
# has access to and can change bar’s return value
end
end
class Foo
prepend BarAfter
end
old 关键词这个想法增加了一个新的关键字类似super,它允许你调用覆盖方法以同样的方式super,您可以调用重载的方法:
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
这样做的主要问题是它向后不兼容:如果您有一个名为的方法old,则将无法再调用它!
superprepended mixin 中的替代方法中的“ 替换”基本上与old此提议中的相同。
redef 关键词与上面类似,但是我们没有添加新的关键字来调用被覆盖的方法并且不用def理会,而是添加了新的关键字来重新定义方法。这是向后兼容的,因为目前语法仍然是非法的:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
除了添加两个新的关键字,我们还可以重新定义superinside 的含义redef:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
redef定义一个方法等效于在prepended mixin中覆盖该方法。super在覆盖方法的行为像super或old该提案。
bind同一个old_method变量时会发生什么?
UnboundMethod#bind都会返回一个新的different Method,因此,无论您是连续两次调用还是两次从不同线程同时调用两次,我都看不到任何冲突。
old和redef?我的2.0.0没有它们。啊,不容错过的其他没有融入Ruby的竞争观念是: