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


180

背景:

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

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

  def format_text(x); ...
end

我想从一个类中调用其中一些方法。通常如何在红宝石中执行以下操作:

class UsefulWorker
  include UsefulThings

  def do_work
    format_text("abc")
    ...
  end
end

问题

include UsefulThings引入的所有方法UsefulThings。在这种情况下,我只想要format_text并且明确地不想get_filedelete_file

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

  1. 以某种方式直接在模块上调用该方法,而无需在任何地方包含它
    • 我不知道如何/是否可以这样做。(因此出现此问题)
  2. 不知何故包括 Usefulthings并且仅引入其中的某些方法
    • 我也不知道该如何/是否可以做到
  3. 创建一个代理类,包括UsefulThings其中,然后委托format_text给该代理实例
    • 这会起作用,但是匿名代理类是一个hack。uck
  4. 将模块分成2个或更多个较小的模块
    • 这也将起作用,并且可能是我能想到的最好的解决方案,但是我宁愿避免使用它,因为最终会产生数十个模块,因此管理起来很麻烦

为什么在一个模块中有很多不相关的功能?它ApplicationHelper来自一个Rails应用程序,我们的团队事实上已将其确定为所有不够具体的东西的垃圾场。大多数情况下,独立实用程序方法随处可见。我可以将其分解为单独的帮手,但其中有30个,每个都只有一种方法...这似乎毫无用处


如果您采用第4种方法(拆分模块),则可以使用Module#included回调方法触发另一个模块,从而使一个模块始终自动包含另一个模块include。该format_text方法可以移到它自己的模块中,因为它似乎很有用。这样可以减轻管理负担。
巴特金斯,2013年

我对模块功能的答案中的所有参考信息感到困惑。假设您有module UT; def add1; self+1; end; def add2; self+2; end; end并且想要使用,add1但没有add2在课堂上使用Fixnum。具有模块功能对此有何帮助?我想念什么吗?
卡里·斯沃夫兰

Answers:


135

如果将模块上的方法转换为模块函数,则可以简单地从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


1
module_function顺便说一句,将方法转换为私有方法,这将破坏其他代码-否则,这将是公认的答案
Orion Edwards

我最终做了一件体面的事情,并将我的代码重构为单独的模块。并没有我想的那么糟。您的答案是仍然可以最正确地解决WRT问题,这是我原来的限制,因此可以接受!
Orion Edwards

@dgtized相关的函数可能一直都停留在一个模块中,这并不意味着我想用所有这些污染我的命名空间。一个简单的例子,如果有一个Files.truncate和一个Strings.truncate,我想在同一个类中显式地使用它们。尽管我不是Ruby开发人员,但每次需要特定方法或修改原始方法时都创建一个新的类/实例并不是一个好方法。
TWiStErRob'7

146

我认为进行一次性一次性调用(不更改现有模块或创建新模块)的最短方法如下:

Class.new.extend(UsefulThings).get_file

2
对于文件erb非常有用。html.erb或js.erb。谢谢 !我想知道这个系统是否浪费内存
AlbertCatalà15年

5
根据我的测试和stackoverflow.com/a/23645285/54247,@AlbertCatalà匿名类与其他所有事物一样被垃圾收集,因此不应浪费内存。
dolzenko

1
如果您不希望将匿名类用作代理,则也可以使用对象作为该方法的代理。Object.new.extend(UsefulThings).get_file
3limin4t0r

82

如果您“拥有”该模块,另一种方法是使用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

26
对于您不拥有它的情况:UtilityThings.send:module_function,:b
Dustin

3
module_function将方法转换为私有方法(无论如何在我的IRB中也是如此),这会破坏其他调用者:-(
Orion Edwards

实际上,至少出于我的目的,我实际上喜欢这种方法。现在,我可以调用ModuleName.method :method_name获取方法对象并通过调用它method_obj.call。否则,我将不得不将方法绑定到原始对象的实例,如果原始对象是Module,则不可能。作为对Orion Edwards的回应,module_function确实将原始实例方法设为私有。ruby-doc.org/core/classes/Module.html#M001642
约翰

2
猎户座-我不相信那是真的。据ruby-doc.org/docs/ProgrammingRuby/html/...,module_function“创建命名方法,模块功能,这些功能可以与模块的接收器被调用,并且也成为可作为实例方法类,在混合模块函数是原始函数的副本,因此可以单独更改。实例方法版本被设为私有。如果不使用任何参数,则随后定义的方法将成为模块函数。”
Ryan Crispin Heneise 2011年

2
您也可以将其定义为def self.a; puts 'aaay'; end
Tilo,

17

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

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

  def self.format_text(x); ...
end

然后您可以致电给他们

UsefulThings.format_text("xxx")

要么

UsefulThings::format_text("xxx")

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


17

要在不包含模块的情况下调用模块实例方法(并且无需创建中间对象):

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

使用这种方法时要小心:与自身的绑定可能无法为该方法提供所需的一切。例如,也许format_text假定由模块,其中(通常)将不存在提供的另一种方法的存在。
内森

这样,就可以加载任何模块,无论模块支持方法是否可以直接加载。甚至最好更改模块级别。但是在某些情况下,这行是发问者想要得到的。
twindai

6

首先,我建议将模块分解为您需要的有用的东西。但是您总是可以创建一个扩展该类的类供您调用:

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

4

A.万一您总是想以一种“限定的”独立方式(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每种方法都更清洁-以防万一。


extend self是一个常见的成语。
斯玛西

4

据我了解的问题,您想将模块的某些实例方法混合到一个类中。

让我们开始考虑Module#include的工作方式。假设我们有一个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是模块时,模块将成为该类的超类。因此,由于继承的工作原理,发送add3到的实例Fixnum将导致Fixnum#add3被调用,并返回dog

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

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

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

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

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

这是我们想要的结果。


1

不知道十年后是否仍然需要它,但是我使用eigenclass解决了。

module UsefulThings
  def useful_thing_1
    "thing_1"
  end

  class << self
    include UsefulThings
  end
end

class A
  include UsefulThings
end

class B
  extend UsefulThings
end

UsefulThings.useful_thing_1 # => "thing_1"
A.new.useful_thing_1 # => "thing_1"
B.useful_thing_1 # => "thing_1"

0

将近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

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

或者,您可以通过Reflection_utils gem 使用它。

spec.add_dependency "reflection_utils", ">= 0.3.0"

require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions

好吧,像我们在此处看到的大多数响应一样,您的方法无法解决原始问题,并且无法加载所有方法。唯一好的答案是将方法与原始模块解除绑定,并将其绑定到目标类中,因为@renier在3年前已经做出了回应。
Joel AZEMAR '18

@JoelAZEMAR我认为您误解了此解决方案。它将被添加到您要使用的模块中。结果,为了使用它们,将不需要包括其任何方法。如OP所建议的,作为可能的解决方案之一:“ 1,以某种方式直接在模块上调用该方法,而不在任何地方包括它”。这就是它的工作方式。
thisismydesign's
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.