我有一个类,可以在其实例上动态定义方法,如下所示
obj = Object.new
obj.define_singleton_method(:foo) { "foo" }
稍后,我想重新定义#foo
,但能够调用原始实现。
使用普通类时,可以通过在预置方法中prepend MyModule
和调用super
来实现这一点。但#prepend
在实例级别不可用。
我用#extend
试过了,但似乎它根本没有覆盖#foo
方法:
obj = Object.new
obj.define_singleton_method(:foo) { "foo" }
mod = Module.new
mod.define_method(:foo) { "module foo" }
obj.extend(mod)
obj.foo
# => "foo"
我研究了 RSpec gem,因为它们对#and_wrap_original
实现了类似的行为(见 https://relishapp.com/rspec/rspec-mocks/v/3-8/docs/configuring-responses/wrapping-the-original-implementation(,但我不在测试环境中,而且看起来需要很多设置代码来实现这种行为(跟踪存根,在重置它们时有回调等(。
那么知道如何在纯 Ruby 中做到这一点吗?
看看obj.singleton_class.ancestors
看看实际发生了什么。默认情况下,它是这样的
[#<Class:#<Object:0x00007febcf103160>>, Object, Kernel, BasicObject]
如果你做obj.extend(mod)
,你会得到这个:
[#<Class:#<Object:0x00007febcf103160>>, #<Module:0x00007febcd0bb088>, Object, Kernel, BasicObject]
所以顺序不好,因为模块"落后于"单例类。为了替换它,它需要在祖先链中处于较早的位置。您可以通过obj.singleton_class.prepend(mod)
.在这种情况下,祖先链是:
[#<Module:0x00007febcd0bb088>, #<Class:#<Object:0x00007febcf103160>>, Object, Kernel, BasicObject]
并且输出显示该方法已被覆盖:
obj.foo
# => "module foo"