我正在开发一种工具,它在类中提供通用功能(称之为Runner
),并且可以使用一种插件系统调用用户定义的代码。对于该工具的任何执行,我需要动态执行由一个或多个插件定义的各种方法。因为Runner
类定义了插件中需要的许多实例级属性,所以我想执行插件方法,就好像它们是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
等来解决这个问题,但是还没有任何工作要做。当然,我对这个工具的其他方法持开放态度,但我也很好奇这是否可以按照上述方式完成。
答案 0 :(得分:2)
我认为使用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
答案 1 :(得分:1)
这应该有效。它动态地包含模块
B.prototype = Object.create(A.prototype);
答案 2 :(得分:1)
您可以使用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
中的每个方法。当Runner
为m
时,这实际上“揭露”m
中被include
覆盖的实例方法。之前,比如Other#do_work
被“覆盖”(不是正确的词,因为该方法仍然存在),在_old_do_work
中创建了别名Runner
。当Other#do_work
被移除时,Plugin1#do_word
被发现,alias_method :do_word, :_old_do_work
既不必要也不可取。{1}}。只应移除alias
。
(要运行上面的代码,必须剪切并粘贴我插入的几乎为空的行分隔的三个部分,以避免垂直滚动。)