我可以在不包含它的情况下调用Ruby模块的实例方法吗?

背景:

我有一个模块,它声明了一些实例方法

module UsefulThings def get_file; ... def delete_file; ... def format_text(x); ... end 

我想从一个class级中调用这些方法中的一些。 通常你如何在ruby中这样做是这样的:

 class UsefulWorker include UsefulThings def do_work format_text("abc") ... end end 

问题

include UsefulThingsUsefulThings 所有的方法。 在这种情况下,我只希望format_text并明确不希望get_filedelete_file

我可以看到几个可能的解决scheme:

  1. 以某种方式直接在模块上调用方法,而不在任何地方包含它
    • 我不知道如何做到这一点。 (因此,这个问题)
  2. 不知何故包括Usefulthings ,只能引入一些方法
    • 我也不知道如何做到这一点
  3. 创build一个代理类,包括UsefulThings ,然后委托format_text到该代理实例
    • 这将工作,但匿名代理类是一个黑客。 呸。
  4. 将模块分成2个或更多个较小的模块
    • 这也是可行的,也许是我能想到的最好的解决scheme,但我宁愿避免它,因为我最终会扩大几十个和几十个模块 – pipe理这将是负担繁重

为什么单个模块中有很多不相关的function? 它是一个来自Rails应用程序的ApplicationHelper ,我们的团队事实上已经决定将这个应用程序作为任何不属于任何其他地方的具体事物的倾倒地。 大多数独立的实用程序方法,到处使用。 我可以把它分解成单独的助手,但是会有30个,每个都有1个方法…这看起来没有什么效果

如果一个模块上的某个方法变成了模块函数,你可以简单地把它叫做Mods,就好像它被声明为一样

 module Mods def self.foo puts "Mods.foo(self)" end end 

下面的module_function方法将避免打破任何包含所有Mod的类。

 module Mods def foo puts "Mods.foo" end end class Includer include Mods end Includer.new.foo Mods.module_eval do module_function(:foo) public :foo end Includer.new.foo # this would break without public :foo above class Thing def bar Mods.foo end end Thing.new.bar 

不过,我很好奇,为什么一组无关的函数都包含在同一个模块中呢?

编辑以显示包含仍然工作,如果public :foo后调用module_function :foo

我认为最简单的方法就是抛弃单个调用(不改变现有的模块或创build新的模块),如下所示:

 Class.new.extend(UsefulThings).get_file 

如果你“拥有”模块的另一种方法是使用module_function

 module UsefulThings def a puts "aaay" end module_function :a def b puts "beee" end end def test UsefulThings.a UsefulThings.b # Fails! Not a module method end test 

如果你想调用这些方法而不在另一个类中包含模块,那么你需要将它们定义为模块方法:

 module UsefulThings def self.get_file; ... def self.delete_file; ... def self.format_text(x); ... end 

然后你可以打电话给他们

 UsefulThings.format_text("xxx") 

要么

 UsefulThings::format_text("xxx") 

但无论如何,我会build议你把相关的方法在一个模块或一个类。 如果你有问题,你只想从模块中包含一个方法,那么这听起来就像是一个糟糕的代码味道,把不相关的方法放在一起是不好的Ruby风格。

调用模块实例方法而不包含模块(并且不创build中间对象):

 class UsefulWorker def do_work UsefulThings.instance_method(:format_text).bind(self).call("abc") ... end end 

答:如果你总是想以“合格的”,独立的方式(UsefulThings.get_file)来调用它们,那么就像别人指出的那样使它们变成静态的,

 module UsefulThings def self.get_file; ... def self.delete_file; ... def self.format_text(x); ... # Or.. make all of the "static" class << self def write_file; ... def commit_file; ... end end 

B.如果你仍然想保持mixin方法在同样的情况下,以及一次性的独立调用,你可以有一个单一的模块,用mixin 扩展自己:

 module UsefulThingsMixin def get_file; ... def delete_file; ... def format_text(x); ... end module UsefulThings extend UsefulThingsMixin end 

所以这两个作品:

  UsefulThings.get_file() # one off class MyUser include UsefulThingsMixin def f format_text # all useful things available directly end end 

恕我直言,它比每个单一的方法都比module_function清洁 – 万一需要所有的方法。

正如我理解的问题,你想混合一些模块的实例方法到一个类。

首先考虑模块#包含的工作方式。 假设我们有一个包含两个实例方法的UsefulThings模块:

 module UsefulThings def add1 self + 1 end def add3 self + 3 end end UsefulThings.instance_methods #=> [:add1, :add3] 

Fixnum include那个模块:

 class Fixnum def add2 puts "cat" end def add3 puts "dog" end include UsefulThings end 

我们看到:

 Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" } #=> [:add2, :add3, :add1] 1.add1 2 1.add2 cat 1.add3 dog 

你是否期待UsefulThings#add3覆盖Fixnum#add3 ,所以1.add3会返回4 ? 考虑一下:

 Fixnum.ancestors #=> [Fixnum, UsefulThings, Integer, Numeric, Comparable, # Object, Kernel, BasicObject] 

当这个类include这个模块时,这个模块就成为这个类的超类。 因此,由于inheritance是如何工作的,向Fixnum的实例发送add3会导致Fixnum#add3被调用,返回dog

现在让我们添加一个方法:add2UsefulThings

 module UsefulThings def add1 self + 1 end def add2 self + 2 end def add3 self + 3 end end 

我们现在希望Fixnum只包含方法add1add3 。 这样做,我们希望得到与上述相同的结果。

假设,如上所述,我们执行:

 class Fixnum def add2 puts "cat" end def add3 puts "dog" end include UsefulThings end 

结果是什么? 不想要的方法:add2添加到Fixnum:add1被添加,并且出于上面解释的原因:add3不添加:add3 。 所以我们所要做的就是undef :add2 。 我们可以用一个简单的辅助方法来做到这一点:

 module Helpers def self.include_some(mod, klass, *args) klass.send(:include, mod) (mod.instance_methods - args - klass.instance_methods).each do |m| klass.send(:undef_method, m) end end end 

我们这样调用:

 class Fixnum def add2 puts "cat" end def add3 puts "dog" end Helpers.include_some(UsefulThings, self, :add1, :add3) end 

然后:

 Fixnum.instance_methods.select { |m| m.to_s.start_with? "add" } #=> [:add2, :add3, :add1] 1.add1 2 1.add2 cat 1.add3 dog 

这是我们想要的结果。

首先,我build议把模块分解成你需要的有用的东西。 但是你总是可以创build一个类来扩展你的调用:

 module UsefulThings def a puts "aaay" end def b puts "beee" end end def test ob = Class.new.send(:include, UsefulThings).new ob.a end test 

近9年后,这是一个通用的解决scheme:

 module CreateModuleFunctions def self.included(base) base.instance_methods.each do |method| base.module_eval do module_function(method) public(method) end end end end RSpec.describe CreateModuleFunctions do context "when included into a Module" do it "makes the Module's methods invokable via the Module" do module ModuleIncluded def instance_method_1;end def instance_method_2;end include CreateModuleFunctions end expect { ModuleIncluded.instance_method_1 }.to_not raise_error end end end 

您需要应用的不幸的技巧是在方法定义之后包含模块。 或者,也可以在上下文被定义为ModuleIncluded.send(:include, CreateModuleFunctions)后包含它。

或者你可以通过reflection_utils gem来使用它。

 spec.add_dependency "reflection_utils", ">= 0.3.0" require 'reflection_utils' include ReflectionUtils::CreateModuleFunctions