如何像调用实例方法一样动态调用模块方法?
How do you dynamically invoke a module method as if it were an instance method?
我正在开发一种工具,它在 class(称之为 Runner
)中提供通用功能,并且可以使用一种插件系统调用用户定义的代码。对于该工具的任何执行,我需要动态执行由一个或多个插件定义的各种方法。因为 Runner
class 定义了插件中需要的许多实例级属性,所以我想 像实例方法一样执行插件方法 Runner
.
这是一个简化的例子:
module Plugin1
def do_work
p ['Plugin1', data]
end
end
module Plugin2
def do_work
p ['Plugin2', data]
end
end
module Plugin3
def do_work
p ['Plugin3', data]
end
end
class Runner
attr_accessor :data # Plugins need access to these.
def initialize(data, *plugins)
@data = data
@plugin_names = plugins.map { |p| "Plugin#{p}" }
end
def run
@plugin_names.each { |name|
mod = Kernel.const_get(name)
plugin_method = mod.instance_method(:do_work)
# How do I call the plugin_method as if it were
# an instance method of the Runner?
}
end
end
# Execute a runner using Plugin3 and Plugin1.
r = Runner.new(987, 3, 1)
r.run
我已经使用 instance_exec
、bind
、module_function
等尝试了各种方法来实现这一目标,但没有任何效果。当然,我对该工具的其他方法持开放态度,但我也很好奇是否可以按照上述方式完成此操作。
这应该有效。它动态包含模块
module Plugin1
def do_work
p ['Plugin1', data]
end
end
module Plugin2
def do_work
p ['Plugin2', data]
end
end
module Plugin3
def do_work
p ['Plugin3', data]
end
end
class Runner
attr_accessor :data # Plugins need access to these.
def initialize(data, *plugins)
@data = data
@plugin_names = plugins.map { |p| "Plugin#{p}" }
end
def run
@plugin_names.each { |name|
mod = Kernel.const_get(name)
Runner.send(:include, mod)
do_work
}
end
end
# Execute a runner using Plugin3 and Plugin1.
r = Runner.new(987, 3, 1)
r.run
我觉得用bind是对的,不知道你为什么觉得不行
module Plugin1
def do_work
p ['Plugin1', data]
end
end
module Plugin2
def do_work
p ['Plugin2', data]
end
end
module Plugin3
def do_work
p ['Plugin3', data]
end
end
class Runner
attr_accessor :data # Plugins need access to these.
def initialize(data, *plugins)
@data = data
@plugin_names = plugins.map { |p| "Plugin#{p}" }
end
def run
@plugin_names.each { |name|
mod = Kernel.const_get(name)
plugin_method = mod.instance_method(:do_work).bind(self)
# How do I call the plugin_method as if it were
# an instance method of the Runner?
plugin_method.call
}
end
end
# Execute a runner using Plugin3 and Plugin1.
r = Runner.new(987, 3, 1)
r.run
你可以使用 Module#alias_method:
module Other
def do_work
puts 'hi'
end
end
module Plugin1
def do_work
p ['Plugin1', data]
end
end
module Plugin2
def do_work
p ['Plugin2', data]
end
end
module Plugin3
def do_work
p ['Plugin3', data]
end
end
class Runner
include Other
attr_accessor :data
def initialize(data, *plugins)
@data = data
@plugin_names = plugins.map { |p| "Plugin#{p}" }
end
def self.save_methods(mod, alias_prefix)
(instance_methods && mod.instance_methods).each { |m|
alias_method :"#{alias_prefix}#{m.to_s}", m }
end
def self.include_module(mod)
include mod
end
def self.remove_methods(mod, alias_prefix)
ims = instance_methods
mod.instance_methods.each do |m|
mod.send(:remove_method, m)
aka = :"#{alias_prefix}#{m.to_s}"
remove_method(aka) if ims.include?(aka)
end
end
def run(meth)
alias_prefix = '_old_'
@plugin_names.each do |mod_name|
print "\ndoit 1: "; send(meth) # included for illustrative purposes
mod_object = Kernel.const_get(mod_name)
self.class.save_methods(mod_object, alias_prefix)
self.class.include_module(mod_object)
print "doit 2: "; send(meth) # included for illustrative purposes
self.class.remove_methods(mod_object, alias_prefix)
end
print "\ndoit 3: "; send(meth) # included for illustrative purposes
end
end
试一试:
r = Runner.new(987, 3, 1)
r.run(:do_work)
#-> doit 1: hi
# doit 2: ["Plugin3", 987]
# doit 1: hi
# doit 2: ["Plugin1", 987]
# doit 3: hi
在每个模块 mod
是 include
d 并且执行任何感兴趣的计算之后,mod.remove_method m
将应用于 mod
中的每个方法。这实际上 "uncovers" Runner
中的实例方法在 m
为 include
d 时被 m
覆盖。比方说,之前 Other#do_work
是 "overwritten"(不是正确的词,因为该方法仍然存在),在 Runner
中创建了一个别名 _old_do_work
。由于在删除 Plugin1#do_word
时会发现 Other#do_work
,因此没有必要也不希望有 alias_method :do_word, :_old_do_work
。只应删除 alias
。
(对于 运行 上面的代码,有必要剪切并粘贴我插入的几乎为空的行分隔的三个部分以避免垂直滚动。)
我正在开发一种工具,它在 class(称之为 Runner
)中提供通用功能,并且可以使用一种插件系统调用用户定义的代码。对于该工具的任何执行,我需要动态执行由一个或多个插件定义的各种方法。因为 Runner
class 定义了插件中需要的许多实例级属性,所以我想 像实例方法一样执行插件方法 Runner
.
这是一个简化的例子:
module Plugin1
def do_work
p ['Plugin1', data]
end
end
module Plugin2
def do_work
p ['Plugin2', data]
end
end
module Plugin3
def do_work
p ['Plugin3', data]
end
end
class Runner
attr_accessor :data # Plugins need access to these.
def initialize(data, *plugins)
@data = data
@plugin_names = plugins.map { |p| "Plugin#{p}" }
end
def run
@plugin_names.each { |name|
mod = Kernel.const_get(name)
plugin_method = mod.instance_method(:do_work)
# How do I call the plugin_method as if it were
# an instance method of the Runner?
}
end
end
# Execute a runner using Plugin3 and Plugin1.
r = Runner.new(987, 3, 1)
r.run
我已经使用 instance_exec
、bind
、module_function
等尝试了各种方法来实现这一目标,但没有任何效果。当然,我对该工具的其他方法持开放态度,但我也很好奇是否可以按照上述方式完成此操作。
这应该有效。它动态包含模块
module Plugin1
def do_work
p ['Plugin1', data]
end
end
module Plugin2
def do_work
p ['Plugin2', data]
end
end
module Plugin3
def do_work
p ['Plugin3', data]
end
end
class Runner
attr_accessor :data # Plugins need access to these.
def initialize(data, *plugins)
@data = data
@plugin_names = plugins.map { |p| "Plugin#{p}" }
end
def run
@plugin_names.each { |name|
mod = Kernel.const_get(name)
Runner.send(:include, mod)
do_work
}
end
end
# Execute a runner using Plugin3 and Plugin1.
r = Runner.new(987, 3, 1)
r.run
我觉得用bind是对的,不知道你为什么觉得不行
module Plugin1
def do_work
p ['Plugin1', data]
end
end
module Plugin2
def do_work
p ['Plugin2', data]
end
end
module Plugin3
def do_work
p ['Plugin3', data]
end
end
class Runner
attr_accessor :data # Plugins need access to these.
def initialize(data, *plugins)
@data = data
@plugin_names = plugins.map { |p| "Plugin#{p}" }
end
def run
@plugin_names.each { |name|
mod = Kernel.const_get(name)
plugin_method = mod.instance_method(:do_work).bind(self)
# How do I call the plugin_method as if it were
# an instance method of the Runner?
plugin_method.call
}
end
end
# Execute a runner using Plugin3 and Plugin1.
r = Runner.new(987, 3, 1)
r.run
你可以使用 Module#alias_method:
module Other
def do_work
puts 'hi'
end
end
module Plugin1
def do_work
p ['Plugin1', data]
end
end
module Plugin2
def do_work
p ['Plugin2', data]
end
end
module Plugin3
def do_work
p ['Plugin3', data]
end
end
class Runner
include Other
attr_accessor :data
def initialize(data, *plugins)
@data = data
@plugin_names = plugins.map { |p| "Plugin#{p}" }
end
def self.save_methods(mod, alias_prefix)
(instance_methods && mod.instance_methods).each { |m|
alias_method :"#{alias_prefix}#{m.to_s}", m }
end
def self.include_module(mod)
include mod
end
def self.remove_methods(mod, alias_prefix)
ims = instance_methods
mod.instance_methods.each do |m|
mod.send(:remove_method, m)
aka = :"#{alias_prefix}#{m.to_s}"
remove_method(aka) if ims.include?(aka)
end
end
def run(meth)
alias_prefix = '_old_'
@plugin_names.each do |mod_name|
print "\ndoit 1: "; send(meth) # included for illustrative purposes
mod_object = Kernel.const_get(mod_name)
self.class.save_methods(mod_object, alias_prefix)
self.class.include_module(mod_object)
print "doit 2: "; send(meth) # included for illustrative purposes
self.class.remove_methods(mod_object, alias_prefix)
end
print "\ndoit 3: "; send(meth) # included for illustrative purposes
end
end
试一试:
r = Runner.new(987, 3, 1)
r.run(:do_work)
#-> doit 1: hi
# doit 2: ["Plugin3", 987]
# doit 1: hi
# doit 2: ["Plugin1", 987]
# doit 3: hi
在每个模块 mod
是 include
d 并且执行任何感兴趣的计算之后,mod.remove_method m
将应用于 mod
中的每个方法。这实际上 "uncovers" Runner
中的实例方法在 m
为 include
d 时被 m
覆盖。比方说,之前 Other#do_work
是 "overwritten"(不是正确的词,因为该方法仍然存在),在 Runner
中创建了一个别名 _old_do_work
。由于在删除 Plugin1#do_word
时会发现 Other#do_work
,因此没有必要也不希望有 alias_method :do_word, :_old_do_work
。只应删除 alias
。
(对于 运行 上面的代码,有必要剪切并粘贴我插入的几乎为空的行分隔的三个部分以避免垂直滚动。)