我有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"
我不明白为什么会这样。有人能帮助我理解这个吗?
答案 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中表示。
它的作用是:
M′
M′
的方法表指针设置为M
的方法表(并且对于常量表和类变量表也是如此)M′
指向C
超类C
的超类指针设置为M′
有效地使M′
成为C
的新超类,并将C
的旧超类设为M′
的超类,或换句话说,插入M′
}在继承树中直接位于C
之上。
为什么这样做?因为它使方法查找变得非常简单:方法查找算法根本不需要了解mixins,它仍然是与没有mixins的语言完全相同的算法:
append_features
传递方法名称和参数(除非方法名称为method_missing
,然后raise
a {{3} (例外)请注意,算法的核心只是步骤2中非常紧凑的while
循环。紧密的简单循环很好,毕竟,方法查找 最常执行的操作面向对象的语言实现。
现在,你可能会说,等等,我已经C
询问了superclass
,而不返回M′
,它返回Object
&#34!;是的,这是真的。但是,method_missing
不会简单地返回超类指针,与方法查找算法不同,它知道mixins。或者,它知道YARV的开发人员调用虚拟类的内容,并且在返回超类时它知道跳过它们。 虚拟类是YARV内部使用的名称。它指的是包含类(即上面描述的类,它们是在include
期间创建的)和单例类。反射方法知道如何特别对待它们,例如NoMethodError
和Class#superclass
忽略它们,Class#superclass
知道返回模块M
而不是包含类M′
。
我会在这里暂停,让你在一张纸上运行代码的append_features
算法和方法查找算法,这样你就可以自己找出为什么你了正在看到你所看到的结果。
好的,那么,在你的例子中看起来如何?
我们的班级ABC
以Object
为超类。 Out current inheritance树看起来像这样:
ABC → Object
现在,我们include
A
。这意味着包含类A′
会在ABC
上方插入:
ABC → A′ → Object
我们使用B
重复该步骤:
ABC → B′ → A′ → Object
让我们现在"运行"方法查找name
:
ABC
。ABC
是否有一个名为name
的方法?不!ABC
的超类指针。它指向B′
B′
是否有一个名为name
的方法?是的,因为它与B
共享方法表,该方法表有一个名为name
的方法。我认为你可以在第二个代码示例中看到它将如何反过来。
注意:这个答案故意忽略了Object#class
和Module#ancestors
的存在,这有点复杂。