在因question duplication而投票结束之前,我想说我的问题非常简单(在上述问题中未提及)。
有两个模块,一个使用extend self
定义模块方法,另一个定义 mixin方法。
module A
extend self
def module_a_meth
"Called module_a_meth"
end
end
module B
def module_b_meth
"Called module_b_meth"
end
end
有一个类,我include
和extend
这些模块:
class Test
include A
extend A
include B
extend B
end
当我们include
模块时,它的方法变成了类'实例方法,当extend
ing - 类方法时。
问题:
如果模块中的方法定义为 module 方法或 mixin 方法,那么对于类来说并不重要,对吗?我的意思是,当include
d - 每个方法(模块方法或mixin方法)变为实例方法,以及extend
时 - 变为类方法
如果我错了 - 区别在哪里?
obj = Test.new
puts obj.module_a_meth
puts obj.module_b_meth
puts Test.module_a_meth
puts Test.module_b_meth
#=> Called module_a_meth
#=> Called module_b_meth
#=> Called module_a_meth
#=> Called module_b_meth
请以是或否开始您的回答,因为我的问题暗示了这种类型的答案:)。
答案 0 :(得分:2)
无论您使用的是extend
还是include
,您始终都会复制实例方法。不同之处在于这些实例方法的存在。
当您调用Class#include
时,您正在将模块中的所有实例方法“复制”为类中的实例方法。它类似于继承的工作方式,如果你调用Class#ancestors
,你会在那里看到模块。
当您调用Object#extend
时,您正在将模块的所有实例方法复制到对象的单例类。这是仅为在运行时创建的此对象实例保留的类。这就是你获得“类方法”的方法(例如MyClass.hello_world
);通过将它们添加到类的单例中。您还可以执行扩展特定对象实例(例如s = String.new; s.extend(SomeModule); s.hello_world
)
还有其他一些差异。根据您使用extend
还是include
,上下文绑定会有所不同。 extend
include
不会导致模块显示在祖先链中。
当尝试添加“类”和实例方法时,您将看到的一个常见模式是使用included
回调来使用ClassMethods
模块扩展基类:
module MyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def hello_world
end
end
end
ActiveSupport::Concerns
也抽象了这种模式,允许你在一次调用中添加实例和“类”方法。
我个人更喜欢让模块只使用实例方法并使用单例方法(例如def self.my_method
)来设置范围方法(类似于如何使用私有方法)。这允许消费者使用他们想要的extend
或include
并使其按预期工作。
我不确定这是否能回答你的问题,但是有一些信息可供你使用
答案 1 :(得分:2)
让我们分步看一下。
module A
puts "self = #{self}"
extend self
def module_a_meth
"Called module_a_meth"
end
end
class Test
end
Test.include A
#-> self = Test
Test.instance_methods.include?(:module_a_meth)
#=> true
Test.methods.include?(:module_a_meth)
#=> false - no class method
因此include
包含:module_a_meth
作为实例方法。由于self
为Test
,行:
extend self
相当于:
extend Test
当然没有提到该模块。现在我们extend
并获得预期结果:
Test.extend A
#=> true
Test.methods.include?(:module_a_meth)
#=> true
including
和extend
ing B
是正常的:
module B
def module_b_meth
"Called module_b_meth"
end
end
Test.include B
Test.instance_methods.include?(:module_b_meth)
#=> true
Test.extend B
Test.methods.include?(:module_b_meth)
#=> true
答案 2 :(得分:1)
首先,关于实际问题:否:)。
类(或任何其他对象)关心如何在您包含的模块中定义方法。基本上,您所描述的模块中的方法被定义为mixin
方法。 extend self
不会将方法重新定义为模块方法,但基本上将它们复制到两个上下文中。
这是一个关于extend
如何运作的问题,这只是一个棘手的案例。
首先,将extend
视为对象的单例类上下文中的include
。这两个定义是相同的:
module SomeModule
def hi
'hi'
end
end
class SomeClass
extend SomeModule
end
class SomeClass
class << self
include SomeModule
end
end
考虑到这一点,在你所说的模块中使用extend self
:采用我定义的所有mixin方法并使用它们扩展模块的单例类。这种魔力是红宝石本质的结果:重新打开任何定义的能力。以下是extend self
的详细版本的样子:
module Module1
def hi
'hi'
end
end
module Module1
extend Module1 # which is self
#### now "hi" is both here:
# def hi; end
#### and here:
# class << self; def hi; end
end
Module1.hi # => 'hi'
class SomeClass; include Module1; end;
SomeClass.new.hi # => 'hi'
__编辑__
快速证明对象关心如何定义模块中的方法:
module SomeModule
def self.hi
'hi'
end
end
object = 'some string'
class << object
include SomeModule
end
object.hi # => NoMethodError: undefined method