我可以调用Ruby模块上的实例方法而不包含它吗? 背景:

我可以调用Ruby模块上的实例方法而不包含它吗? 背景:,ruby,module,methods,Ruby,Module,Methods,我有一个模块,它声明了许多实例方法 module UsefulThings def get_file; ... def delete_file; ... def format_text(x); ... end 我想从类中调用其中的一些方法。在ruby中通常是这样做的: class UsefulWorker include UsefulThings def do_work format_text("abc") ... end end class Fi

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

module UsefulThings
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end
我想从类中调用其中的一些方法。在ruby中通常是这样做的:

class UsefulWorker
  include UsefulThings

  def do_work
    format_text("abc")
    ...
  end
end
class Fixnum
  def add2
    puts "cat"
  end
  def add3
    puts "dog"
  end
  Helpers.include_some(UsefulThings, self, :add1, :add3)
end
问题
include UsefulThings
引入了
UsefulThings
中的所有方法。在这种情况下,我只希望
格式化\u文本
,而明确不希望
获取\u文件
删除\u文件

我可以看到几种可能的解决方案:

  • 以某种方式直接在模块上调用该方法,而不将其包含在任何地方
    • 我不知道如何/是否能做到这一点。(因此提出这个问题)
  • 以某种方式包含有用的东西,并且只引入它的一些方法
    • 我也不知道如何/是否能做到这一点
  • 创建一个代理类,在其中包含
    有用的东西
    ,然后将
    格式化\u text
    委托给该代理实例
    • 这是可行的,但匿名代理类是一种黑客行为。恶心
  • 将模块拆分为2个或更多更小的模块
    • 这也会起作用,可能是我能想到的最好的解决方案,但我更愿意避免它,因为我最终会有几十个模块的激增——管理这将是一项繁重的工作

  • 为什么在一个模块中有很多不相关的功能?这是来自rails应用程序的
    ApplicationHelper
    ,我们的团队事实上已经决定将其作为任何不够具体而不属于其他任何地方的东西的垃圾场。大多数是独立的实用程序方法,在任何地方都可以使用。我可以把它分解成不同的助手,但是会有30个,每个都有一个方法。。。这似乎是徒劳的

    首先,我建议将模块分解为您需要的有用内容。但您始终可以为调用创建一个扩展该类的类:

    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
    

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

    module UsefulThings
      def self.get_file; ...
      def self.delete_file; ...
    
      def self.format_text(x); ...
    end
    
    module UsefulThings
      def add1
        self + 1
      end
      def add3
        self + 3
      end
    end
    
    UsefulThings.instance_methods
      #=> [:add1, :add3]
    
    然后你可以打电话给他们

    UsefulThings.format_text("xxx")
    


    但无论如何,我建议您将相关方法放在一个模块或一个类中。如果您不想只包含模块中的一个方法,那么这听起来像是一种糟糕的代码味道,将不相关的方法放在一起不是一种好的Ruby风格。

    如果您“拥有”模块,另一种方法是使用
    module\u函数

    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
    

    如果一个模块上的一个方法被转换成一个模块函数,你可以简单地从Mods中调用它,就像它被声明为

    module Mods
      def self.foo
         puts "Mods.foo(self)"
      end
    end
    
    下面的模块功能方法将避免破坏包含所有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  
    
    然而,我很好奇为什么一组不相关的函数首先都包含在同一个模块中


    编辑显示,如果在
    module\u function:foo

    之后调用
    public:foo
    ,则包含仍然有效。我认为最短的方法是丢弃单个调用(不更改现有模块或创建新模块),如下所示:

    Class.new.extend(UsefulThings).get_file
    

    答:如果你总是想用一种“限定的”独立的方式(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
    
    所以这两种方法都有效:

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

    对于每个方法,它比
    module\u函数
    更简洁,以防需要所有方法。

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


    我理解这个问题,您希望将一些模块的实例方法混合到一个类中

    让我们从考虑如何工作开始。假设我们有一个模块
    UsefulThings
    ,其中包含两个实例方法:

    module UsefulThings
      def self.get_file; ...
      def self.delete_file; ...
    
      def self.format_text(x); ...
    end
    
    module UsefulThings
      def add1
        self + 1
      end
      def add3
        self + 3
      end
    end
    
    UsefulThings.instance_methods
      #=> [:add1, :add3]
    
    Fixnum
    包括该模块:

    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] 
    
    当类
    包含模块时,模块成为类的超类。因此,由于继承的工作方式,将
    add3
    发送到
    Fixnum
    的实例将导致调用
    Fixnum\add3
    ,返回
    dog

    现在,让我们添加一个方法
    :将2
    添加到
    有用的东西中

    module UsefulThings
      def add1
        self + 1
      end
      def add2
        self + 2
      end
      def add3
        self + 3
      end
    end
    
    我们现在希望
    Fixnum
    只包含
    add1
    add3
    方法。如果这样做,我们希望得到与上述相同的结果

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

    class Fixnum
      def add2
        puts "cat"
      end
      def add3
        puts "dog"
      end
      include UsefulThings
    end
    
    结果如何?不需要的方法
    :add2
    被添加到
    Fixnum
    ,添加了
    :add1
    ,并且由于我上面解释的原因,没有添加
    :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 UsefulWorker
      include UsefulThings
    
      def do_work
        format_text("abc")
        ...
      end
    end
    
    class Fixnum
      def add2
        puts "cat"
      end
      def add3
        puts "dog"
      end
      Helpers.include_some(UsefulThings, self, :add1, :add3)
    end
    
    然后:


    这就是我们想要的结果。

    经过将近9年的时间,这里有一个通用的解决方案:

    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
    
    您需要应用的不幸技巧是在定义了方法之后包含模块。或者,您也可以在上下文定义为
    moduleinclude.send(:include,CreateModuleFunctions)
    后包含它

    或者你可以通过gem使用它


    不确定10年后是否有人还需要它,但我用eigenclass解决了这个问题

    模块有用的东西
    定义有用的东西1
    “事情1”
    结束
    类“thing_1”
    新的有用的东西
    B.有用的东西
    
    对于您不拥有它的情况:UsefulThings.send:module_函数,:bmodule_函数将该方法转换为私有方法(在我的IRB中也是如此),这将中断其他调用方:-(至少出于我的目的,我实际上喜欢这种方法。现在我可以调用
    ModuleName.method:method\u name
    来获取一个method对象,并通过
    method\u obj.call
    来调用它。否则我将不得不绑定
    spec.add_dependency "reflection_utils", ">= 0.3.0"
    
    require 'reflection_utils'
    include ReflectionUtils::CreateModuleFunctions