2015-05-19 90 views
1

我正在研究一个在类中提供常用功能的工具(称为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_execbindmodule_function,等等,但没有得到任何工作。当然,我对这个工具的其他方法持开放态度,但我也很好奇这是否可以按照上述方式完成。

+0

我做了重大修改,一旦我想通了一些关于'包含'模块的细节。 –

回答

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 mmod施加到每个方法。这实际上“揭示”了当minclude时被m覆盖的Runner中的实例方法。之前,例如,Other#do_work被“覆盖”(不是正确的词,因为该方法仍然存在),在Runner中制作了别名_old_do_work。由于在Plugin1#do_word被删除时被发现,因此alias_method :do_word, :_old_do_work既不必要也不需要。只有alias应该被删除。

(要运行上面的代码,就必须剪切和粘贴三个部分由我插入避免垂直滚动,几乎是空的线划分。)

1

这应该有效。它动态包括模块

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 
+0

谢谢。也许我的简单例子太简单了。由于担心用户定义的方法可能会相互冲突,我一直避免直接使用'include'。从某种意义上说,'include'会在** Runner中带来太多的**。 – FMc

+0

啊好吧,这是有道理的。我不确定如果没有使Runner成为单例,或者创建一个包装Runner的类,因为“@ data”是一个实例方法,并且据我所知,无法访问实例方法一个类定义(你甚至可以在类内部,外部方法和方法内部定义'@ data',并且由于范围的原因它们本质上是两个不同的变量)。我可能是错的,所以请让我知道,如果你想出点什么! – chintanparikh

2

我认为这是正确的使用绑定,但我不知道为什么你认为它没有工作

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 
+0

当我尝试这种方式时出现此错误:“绑定参数必须是Plugin3(TypeError)的实例”。 – FMc

+0

我只是复制粘贴到一个文件,运行它,它的工作原理(ruby 2.2.0) –

+0

这可能是红宝石版本的问题。我不确定,但它适用于我。我是红宝石2.1.0 – ShallmentMo

相关问题