Ruby 如何像调用实例方法一样动态调用模块方法?
我正在开发一个在类中提供公共功能的工具(称之为Ruby 如何像调用实例方法一样动态调用模块方法?,ruby,metaprogramming,Ruby,Metaprogramming,我正在开发一个在类中提供公共功能的工具(称之为Runner),它可以使用一种插件系统调用用户定义的代码。对于该工具的任何执行,我都需要动态执行由一个或多个插件定义的各种方法。因为Runner类定义了插件中需要的许多实例级属性,所以我希望执行插件方法,就像它们是Runner的实例方法一样 以下是一个简化的示例: module Plugin1 def do_work p ['Plugin1', data] end end module Plugin2 def do_work
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
我用
实例执行
,绑定
,模块函数
,等等尝试了各种方法来实现这一点,但没有任何效果。当然,我对该工具的其他方法持开放态度,但我也很好奇是否可以用上面描述的方式来实现这一点。这应该是可行的。它动态地包含模块
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
这应该行得通。它动态地包含模块
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
我认为使用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
您可以使用:
试试看:
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\u方法m
应用于mod
中的每个方法。这实际上“揭示”了Runner
中的实例方法,当m
为include
d时,该实例方法被m
覆盖。比如说,Other#do#u work
被“覆盖”(不是正确的词,因为方法仍然存在),在Runner
中生成了一个别名\u old#u do_work
。当删除Plugin1#do#u word
时,由于Other#do#u work
被发现,因此没有必要也不希望使用alias_方法:do#u word,:_old_do#u work
。只应删除别名
(要运行上面的代码,有必要剪切并粘贴被我插入的几乎空行分割的三个部分,以避免垂直滚动。)您可以使用:
试试看:
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\u方法m
应用于mod
中的每个方法。这实际上“揭示”了Runner
中的实例方法,当m
为include
d时,该实例方法被m
覆盖。比如说,Other#do#u work
被“覆盖”(不是正确的词,因为方法仍然存在),在Runner
中生成了一个别名\u old#u do_work
。当删除Plugin1#do#u word
时,由于Other#do#u work
被发现,因此没有必要也不希望使用alias_方法:do#u word,:_old_do#u work
。只应删除别名
(要运行上面的代码,有必要剪切并粘贴被我插入的几乎空行分割的三个部分,以避免垂直滚动。)谢谢。也许我简化的例子太简单了。我一直避免直接使用
include
,因为担心用户定义的方法可能会相互冲突。从某种意义上说,include
给Runner
带来了太多的麻烦。啊,好吧,这是有道理的。我不确定,如果不将Runner设置为singleton,或者不创建一个封装Runner的类,这是可能的,因为@data
是一个实例方法,据我所知,无法从类定义访问实例方法(您甚至可以在类内部、方法外部和方法内部定义@data
,因为范围不同,它们本质上是两个不同的变量)。不过,我可能错了,所以如果你有什么发现,请告诉我!谢谢。也许我的简化示例太简单了。我一直在避免直接使用include
,因为担心用户定义的方法可能会相互冲突。从某种意义上说,include
给运行程序带来了太多麻烦。啊,好吧,这是有道理的。我不确定不让Runner成为一个单例,或者不创建一个包装Runner的类是否可能,因为@data
是一个实例方法,据我所知,无法从类定义访问实例方法(您甚至可以在类内部、方法外部和方法内部定义@data
,由于作用域的原因,它们本质上是两个不同的变量)。不过,我可能是错的,所以如果您发现了什么,请让我知道!当我以这种方式尝试时,会出现此错误:“bind参数必须是Plugin3(typerror)的实例”.我只是把它复制粘贴到一个文件中,运行它,它就可以工作了(ruby 2.2.0)这可能是ruby版本的问题。我不确定,但它对我有效。我的版本是ruby 2.1。0@ShallmentMo我仍然停留在1.9.3上,但我希望今晚晚些时候会用当前版本进行实验。谢谢。@ShallmentMo刚刚发现了这个非常好的讨论,它记录了ruby 2.0中开始的行为变化:(参见“重新绑定模块方法”当我这样尝试时,我得到了这个错误:“bind参数必须是Plugin3的一个实例(TypeError)”。我只是将它复制粘贴到一个文件中,运行它,它就可以工作了(ruby 2.2.0)这可能是ruby版本的问题。我不确定,但它对我有效。我的版本是ruby 2.1。0@ShallmentMo我仍然停留在1.9.3上,但我希望今晚晚些时候会用当前版本进行实验。谢谢。@ShallmentMo刚刚发现了这个非常好的讨论,它记录了ruby 2.0中开始的行为变化:(参见“重新绑定模块方法”我做了一次大的修改,一次我发现了关于include
ing模块的一些细微之处。我做了一次大的修改,一次我发现了一些