在模块中使用"extend self"

4
在因为问题 重复而投票关闭之前,我想说我的问题确实很简单(不是上述提到的问题之一)。
有两个模块,一个使用extend self定义模块方法,另一个定义混入方法
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

有一个类,我同时使用includeextend这些模块:

class Test
  include A
  extend A
  include B
  extend B
end

当我们使用include导入模块时,它的方法变成类的实例方法;当使用extend扩展时,它的方法变成类方法。
问题: 对于类来说,如果模块中的方法被定义为模块方法混合方法都没有关系,是吗?我的意思是,当使用include导入时,每个方法(无论是模块方法还是混合方法)都变成了实例方法,而当使用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

编辑

请以开始你的回答,因为我的问题暗示了这种类型的答案 :).


3
准备一些爆米花,等待 Jorg ... - Anthony
我实在不太明白这个问题。你可以用另一种方式陈述这个问题吗? - Iuri G.
3个回答

4
无论您使用 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,并按预期工作。不确定这是否回答了您的问题,但这是一些信息提供给您。

3

让我们分步骤来看这个问题。

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作为实例方法包含进来。由于selfTest,所以这行代码:

 extend self

等同于:

 extend Test

当然,这没有涉及到该模块。现在我们进行扩展并获得了预期的结果:

Test.extend A
  #=> true 

Test.methods.include?(:module_a_meth)
  #=> true

包含扩展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

感谢您的回答!我的问题在于对 extend self 概念的误解(正如我在接受的答案下评论的那样)。无论如何,还是要感谢您的帮助! - Andrey Deineko

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,你就是在说:将我定义的所有混入方法扩展到模块的单例类中。这种魔法是Ruby天生的特性:重新打开任何定义。下面是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

好的,我的问题在于我误解了extend self的概念 - 它不会将模块中定义的每个方法转换为模块方法,而是使任何方法既成为mixin方法,又成为模块方法。 - Andrey Deineko
是的,Module1.methods.include?(:hi) #=> true(与 Module1.singleton_class.instance_methods.include?(:hi) #=> true 相同),但是 SomeClass.include Module1 不会使该方法成为 SomeClass 的类方法:SomeClass.hi #=> NoMethodError: undefined method 'hi' for SomeClass:Class - Cary Swoveland
Audrey,你的解释是不正确的。extend self 会评估为 extend Test,它没有引用模块,所以显然不能在类上 extend 模块。 - Cary Swoveland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接