[英]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
。 因此,在扩展类时,您不应使用prepend
或include
。
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.