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模块的一些细微之处。我做了一次大的修改,一次我发现了一些