我不明白mixins是如何工作的

时间:2015-09-28 06:59:38

标签: ruby

我有2个模块:

module A
  def name
    puts "REAL"
  end
end

module B
  def name
    puts "FAKE"
  end
end

当我将它们包含在我的课程中时如下:

class ABC
  include A
  include B
end

ABC.new.name的输出将是:

"FAKE"

但是当我包含以下模块时:

class ABC
  include B
  include A
end

ABC.new.name的输出将是:

"REAL"

我不明白为什么会这样。有人能帮助我理解这个吗?

3 个答案:

答案 0 :(得分:4)

虽然两个模块都定义了具有相同名称的方法,但实际保留在您的类中的方法是最后一个包含的方法。

答案 1 :(得分:1)

我没有这种行为:

irb(main):121:0* module A
irb(main):122:1>   def name
irb(main):123:2>     puts "REAL"
irb(main):124:2>   end
irb(main):125:1> end
=> nil
irb(main):128:0* module B
irb(main):129:1>   def name
irb(main):130:2>     puts "FAKE"
irb(main):131:2>   end
irb(main):132:1> end
=> nil
irb(main):133:0> class ABC
irb(main):134:1>   include A
irb(main):135:1>   include B
irb(main):136:1> end
=> ABC
irb(main):137:0> ABC.new.name
FAKE
=> nil
irb(main):143:0> class XYZ
irb(main):144:1>   include B
irb(main):145:1>   include A
irb(main):146:1> end
=> XYZ
irb(main):147:0> XYZ.new.name
REAL
=> nil

哪个有意义 - 包含的最后一个模块可以定义方法。也许您的计划还有其他事情发生?

答案 2 :(得分:0)

让我们后退一步:什么是mixin? Gilad Bracha并没有发明mixins(它们是在Lisp Machine Lisp的 Flavors 面向对象扩展中发明的设计模式),但是他写了关于它们的开创性论文(他的博士论文{ {3}}),其中定义 mixins是什么,并表明mixin组合包含所有其他形式的经典继承。根据这篇论文,mixin是一个由其超类参数化的类。因此,您可以将mixin视为函数:: Class → Class

这究竟是什么意思?好吧,因为mixin是由它的超类参数化的,所以它不会知道它的超类。当mixin混合到一个类中时,它会被提供给它的超类。 mixin可以在继承图中多次混合,每次都使用不同的超类。注意:这与多重继承完全相同:在多重继承中,类只存在一次,但可能有多个超类。在mixin组合中,mixin存在多次次,但每个实例只有一个超类。

这在Ruby中是如何工作的?

让我们再退后一步,看看Ruby中的模块在操作上是什么样的。一个模块有:

  • 方法表
  • 一个常数表
  • 一个类变量表

班级看起来像什么?一个类IS-A模块,所以它具有上述所有功能,此外还有:

  • 超类指针

当您include模块M进入类C时会发生什么?

class C
  include M
end

嗯,首先,"The Programming Language 'Jigsaw': Mixins, Modularity and Multiple Inheritance"是一种方法,就像任何其他方法一样。它没什么特别之处。它的默认实现看起来有点像这样:

class Module
  def include(mod)
    mod.append_features(self)
    included(mod)
  end
end

所以,最后,它调用了Module#include钩子方法,但我们在这里会忽略它。它将实际的mixin成分委托给mixin。 (这意味着mixin可以通过覆盖默认的Module#append_features来决定它是如何混入的!然而,这只是很少使用,但是例如ActiveSupport::Concern使用它进行元编程。)

现在我们刚才提出了问题。 included做了什么?好吧,它再次像其他方法一样,它可以被覆盖,它可以被猴子修补,它可以删除(一个好主意!)。但是,它的默认实现不能在Ruby中表示。

它的作用是:

  1. 创建一个新课程,让我们称之为M′
  2. M′的方法表指针设置为M的方法表(并且对于常量表和类变量表也是如此)
  3. 设置M′指向C超类
  4. 的超类指针
  5. C的超类指针设置为M′
  6. 有效地使M′成为C的新超类,并将C的旧超类设为M′的超类,或换句话说,插入M′ }在继承树中直接位于C之上。

    为什么这样做?因为它使方法查找变得非常简单:方法查找算法根本不需要了解mixins,它仍然是与没有mixins的语言完全相同的算法:

    1. 获取接收者的类指针
    2. 如果类有方法,则执行它,否则,获取超类指针并重复步骤2
    3. 如果类指针为空,则调用append_features传递方法名称和参数(除非方法名称为method_missing,然后raise a {{3} (例外)
    4. 请注意,算法的核心只是步骤2中非常紧凑的while循环。紧密的简单循环很好,毕竟,方法查找 最常执行的操作面向对象的语言实现。

      现在,你可能会说,等等,我已经C询问了superclass,而返回M′,它返回Object&#34!;是的,这是真的。但是,method_missing不会简单地返回超类指针,与方法查找算法不同,知道mixins。或者,它知道YARV的开发人员调用虚拟类的内容,并且在返回超类时它知道跳过它们。 虚拟类是YARV内部使用的名称。它指的是包含类(即上面描述的类,它们是在include期间创建的)和单例类。反射方法知道如何特别对待它们,例如NoMethodErrorClass#superclass忽略它们,Class#superclass知道返回模块M而不是包含类M′

      我会在这里暂停,让你在一张纸上运行代码的append_features算法和方法查找算法,这样你就可以自己找出为什么你了正在看到你所看到的结果。

      好的,那么,在你的例子中看起来如何?

      我们的班级ABCObject为超类。 Out current inheritance树看起来像这样:

      ABC → Object
      

      现在,我们include A。这意味着包含类A′会在ABC上方插入:

      ABC → A′ → Object
      

      我们使用B重复该步骤:

      ABC → B′ → A′ → Object
      

      让我们现在"运行"方法查找name

      1. 获取对象的类指针。它指向ABC
      2. ABC是否有一个名为name的方法?不!
      3. 获取ABC的超类指针。它指向B′
      4. B′是否有一个名为name的方法?是的,因为它与B共享方法表,该方法表有一个名为name的方法。
      5. 我们已经完成了,我们找到了它。执行它!
      6. 我认为你可以在第二个代码示例中看到它将如何反过来。

        注意:这个答案故意忽略了Object#classModule#ancestors的存在,这有点复杂。