繁体   English   中英

Ruby 2.6:在添加模块时如何动态覆盖实例方法?

[英]Ruby 2.6: How can I dynamically override instance methods when prepending a module?

我有一个名为Notifier的模块。

module Notifier
  def self.prepended(host_class)
    host_class.extend(ClassMethods)
  end

  module ClassMethods
    def emit_after(*methods)
      methods.each do |method|
        define_method(method) do |thing, block|
          r = super(thing)
          block.call
          r
        end
      end
    end
  end
end

它公开了一个类方法emit_after 我这样使用它:

class Player
  prepend Notifier
  attr_reader :inventory

  emit_after :take

  def take(thing)
    # ...
  end
end

目的是通过调用emit_after :take ,该模块使用其自己的方法覆盖#take

但是实例方法并未被覆盖。

但是,我可以显式重写它,而无需使用ClassMethods

module Notifier
  def self.prepended(host_class)
    define_method(:take) do |thing, block|
      r = super(thing)
      block.call
      r
    end
  end

class Player
  prepend Notifier
  attr_reader :inventory

  def take(thing)
    # ...
  end
end

#> @player.take @apple, -> { puts "Taking apple" }
#Taking apple
#=> #<Inventory:0x00007fe35f608a98...

我知道ClassMethods#emit_after被调用,所以我假设该方法已定义,但从未被调用。

我想动态创建方法。 如何确保generate方法覆盖我的实例方法?

那么这个解决方案呢:

module Notifier
  def self.[](*methods)
    Module.new do
      methods.each do |method|
        define_method(method) do |thing, &block|
          super(thing)
          block.call if block
        end
      end
    end
  end
end

class Player
  prepend Notifier[:take]

  def take(thing)
    puts "I'm explicitly defined"
  end
end

Player.new.take(:foo) { puts "I'm magically prepended" }
# => I'm explicitly defined
# => I'm magically prepended

它与Aleksei Matiushkin的解决方案非常相似,但是祖先的链条比较干净(那里没有“无用的”通知程序)

@Konstantin Strukov的解决方案很好,但可能有些混乱。 因此,我建议另一种解决方案,该解决方案更像原始解决方案。

您的第一个目标是向您的类添加一个类方法emit_after )。 为此,您应该使用无需任何钩子(例如self.prepended()self.included()self.extended() extend方法。

prepend以及include用于添加或覆盖实例方法 但这是您的第二个目标,它发生在您调用emit_after 因此,在扩展类时,您不应使用prependinclude

module Notifier
  def emit_after(*methods)
    prepend(Module.new do
      methods.each do |method|
        define_method(method) do |thing, &block|
          super(thing)
          block.call if block
        end
      end
    end)
  end
end

class Player
  extend Notifier

  emit_after :take

  def take(thing)
    puts thing
  end
end

Player.new.take("foo") { puts "bar" }  
# foo
# bar
# => nil

现在很明显,您可以调用extend Notifier来添加emit_after类方法,并且所有魔术都隐藏在该方法中。

在当前打开的课程之前:

module Notifier
  def self.prepended(host_class)
    host_class.extend(ClassMethods)
  end

  module ClassMethods
    def emit_after(*methods)
    # ⇓⇓⇓⇓⇓⇓⇓ HERE  
      prepend(Module.new do
        methods.each do |method|
          define_method(method) do |thing, block = nil|
            super(thing).tap { block.() if block }
          end
        end
      end)
    end
  end
end

class Player
  prepend Notifier
  attr_reader :inventory

  emit_after :take

  def take(thing)
    puts "foo"
  end
end

Player.new.take :foo, -> { puts "Taking apple" }
#⇒ foo
#  Taking apple

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM