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

背景:

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

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

问题

include UsefulThingsUsefulThings中引入方法的所有。在这种情况下,我只需要format_text,显式地不需要get_filedelete_file

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

  1. 以某种方式直接在模块上调用方法,而不包括在任何地方
    • 我不知道这能不能做到。(因此产生了这个问题)
    • 李< / ul > < / > 以某种方式包含Usefulthings并只引入它的一些方法
      • 我也不知道该怎么做
      • 李< / ul > < / > 创建一个代理类,包括UsefulThings,然后将format_text委托给该代理实例
        • 这可以工作,但匿名代理类是一种hack。讨厌的东西。
        • 李< / ul > < / >
        • 将模块拆分为2个或更多更小的模块
          • 这也可以工作,可能是我能想到的最好的解决方案,但我宁愿避免它,因为我最终会有几十个模块的扩散-管理这将是负担
          • 李< / ul > < / >

为什么一个模块中有很多不相关的功能?它是来自rails应用程序的ApplicationHelper,我们的团队实际上已经决定将它作为任何不够特定的东西的垃圾场。大多数是独立的实用程序方法,在任何地方都可以使用。我可以把它分解成独立的助手,但有30个,每个都有一个方法……这似乎没有什么效果

157857 次浏览

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

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包含在另一个类中,那么你需要将它们定义为模块方法:

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风格。

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

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

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

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

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_function :foo之后调用public :foo,则包含仍然有效

我认为丢弃单个调用(不改变现有模块或创建新模块)的最短方法如下:

Class.new.extend(UsefulThings).get_file

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更干净——以防需要所有方法。

调用模块实例方法时不包含模块(也不创建中间对象):

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

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

让我们首先考虑# include模块是如何工作的。假设我们有一个模块UsefulThings,它包含两个实例方法:

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


UsefulThings.instance_methods
#=> [:add1, :add3]

Fixnum includes该模块:

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]

当类__abc0模块时,模块就成为类的超类。因此,由于继承的工作方式,将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只对方法add1add3进行include。这样做,我们期望得到与上面相同的结果。

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

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

结果是什么?不需要的方法:add2被添加到Fixnum中,:add1被添加,由于上面解释的原因,:add3没有被添加。所以我们所要做的就是undef :add2。我们可以用一个简单的helper方法来做到这一点:

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

这就是我们想要的结果。

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宝石来使用它。

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


require 'reflection_utils'
include ReflectionUtils::CreateModuleFunctions

不确定10年后是否有人还需要它,但我用特征类解决了它。

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"

今天我在学习Ruby的时候,发现这个老问题很有趣,所以我想用我的新知识来回答。

假设您拥有该模块

module MyModule
def say
'I say'
end


def cheer
'I cheer'
end
end

然后调用类Animal,我可以从MyModule中获取cheer方法,如下所示

class Animal
define_method(:happy, MyModule.method(:cheer))
end

这就是所谓的unbound方法,所以你可以把一个可调用的对象绑定到另一个地方。

从这里开始,您可以像往常一样使用该方法,例如

my_dog = Animal.new


my_dog.happy # => "I cheer"

希望这对你有所帮助,因为我今天也学到了一些新东西。

为了进一步了解,你可以使用irb并查看Method对象。