【问题标题】:Overwrite instance method defined with #define_singleton_method and call original覆盖用#define_singleton_method 定义的实例方法并调用原始方法
【发布时间】:2019-10-04 06:47:13
【问题描述】:

我有一个类可以像这样在其实例上动态定义方法

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 中做到这一点吗?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    查看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"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-06
      • 2020-12-25
      • 2021-04-30
      • 2011-01-04
      • 1970-01-01
      • 2012-07-19
      • 1970-01-01
      • 2010-09-22
      相关资源
      最近更新 更多