Module
是Class
的超类:
Class.superclass
# => Module
在OOP中,这意味着Class
的实例可以在可以使用Module
的实例的每个地方使用。
令人惊讶的是,Ruby中的Class
实例不是这种情况:
class C end
c = C.new
module M end
# Let's do all the extend/include/prepend stuff with M!
c.extend M
C.include M
C.prepend M
# All worked fine until this line.
# Let's turn to classes now!
# First, get a class to work with.
class C_as_M end
C_as_M.class.superclass
# => Module # yes, C_as_M is an instance of a Module child
C_as_M.is_a? Module
# => true # yes, it is still a Module
# And now let's do the same extend/include/prepend stuff with C_as_M!
c.extend C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.include C_as_M
# => TypeError: wrong argument type Class (expected Module)
C.prepend C_as_M
# => TypeError: wrong argument type Class (expected Module)
违反此OOP原则的原因是什么?为什么不能将类用作模块?
答案 0 :(得分:2)
在OOP中,这意味着可以在可以使用Module实例的每个位置使用Class实例。
您在混淆子类型和子类,即子类型化(与合同的细化有关)和继承性(与差分代码的重用有关)。 / p>
在Ruby中,继承会创建一个子类,但不会创建子类型。 (实际上,“类型”是只存在于Ruby中的程序员头脑中的概念。)
Class < Module
是不是子类型的子类的一个示例,而StringIO
<:IO
是不是子类的子类型的一个示例。
违反此OOP原则的原因是什么?
这不是面向对象的原则。子类型和OO完全正交。有没有子类型的OO语言以及有子类型的非OO语言。
注意:实际上,将模块和类合而为一很容易。它们可以相互继承,就像类从类继承以及类从模块继承一样。
答案 1 :(得分:1)
我想,与此同时,我已经弄清楚了为什么类不能在Ruby中用作模块。
或者更具体地说:为什么不能包含/添加类。
与Ruby不支持多重继承的原因相同:
避免祖先层次结构中的歧义/复杂性。
简短地解释了多重继承如何影响祖先的等级之后,我将解释为什么包含/前置类会通过后门引入多重继承或类似的复杂性。
具有单一继承,给定类的祖先层次只是一个链。
该链可能长或短,但始终只是祖先类的线性链:
File.ancestors
=> [File, IO, File::Constants, Enumerable, Object, Kernel, BasicObject]
Object.ancestors
=> [Object, Kernel, BasicObject]
BasicObject.ancestors
=> [BasicObject]
因此,查找实例变量或方法很简单:在当前类中查找;如果找不到,请转到下一个祖先,然后在那查看;如果找不到,请转到下一个祖先,然后在那寻找...
具有多重继承,祖先的层次结构可以分支。
假设地给出
class A end
class B < A; end
class C < A; end
class D < B, C
end
生成以下D类的祖先图:
这会增加复杂性和歧义性,并引发"the diamond problem":
Ruby旨在避免这种复杂性。因此,在设计上不会有多重继承。
包含/添加模块
是构建/操纵祖先等级制度的另一种方式:
class MyBase < BasicObject; end
class C < MyBase; end
C.ancestors
=> [C, MyBase, BasicObject]
module IncludeMe end
C.include IncludeMe
C.ancestors
=> [C, IncludeMe, MyBase, BasicObject]
module PrependMe end
C.prepend PrependMe
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, BasicObject]
module Intermediate end
MyBase.include Intermediate
C.ancestors
=> [PrependMe, C, IncludeMe, MyBase, Intermediate, BasicObject]
包含/前置模块仅使祖先链保持简单链。
没有坏事发生。
包含/添加课程
现在假设IncludeMe
,PrependMe
和Intermediate
不是模块而是类。
为简单起见,我将只上一堂课:
class PrependMe
def to_s
"Hello from prepended #{super}!"
end
end
请记住,PrependMe
默认是从Object
继承的:
PrependMe.ancestors
# => [PrependMe, Object, Kernel, BasicObject]
此外,请注意以下事实:在Ruby中无法创建无基础类( BasicObject 是唯一的无基础类):
class BaselessClass < nil # Try it!
end # You'll fail.
# => TypeError: superclass must be a Class (NilClass given)
因此,每个类 X (除 BasicObject 之外)都有一个祖先链,该祖先链至少包含两个部分,
总是以 BasicObject 结尾:
X.ancestors
# => [X, ..., BasicObject]
# the shortest possible chain is [X, BasicObject]
# the usual chain is [X, ..., Object, Kernel, BasicObject]
那么,在C.prepend PrependMe
之后,类C的祖先层次应该是什么样?
C.ancestors
=> [PrependMe, Object, Kernel, C, MyBase, BasicObject] ?
=> [PrependMe, C, Object, Kernel, MyBase, BasicObject] ?
=> [PrependMe, C, MyBase, Object, Kernel, BasicObject] ?
=> [PrependMe, C, MyBase, BasicObject] ? # Object and Kernel omitted on purpose
还是祖先的体系甚至应该在PrependMe
分支到Object
的分支?具有钻石问题的所有影响。
每个选项都有合理的理由。
答案取决于您想从(c = C.new).to_s
的结果中看到什么。
很显然,包含/前置类将导致歧义性和复杂性与多重继承相类似甚至更差。就像在多重继承的情况下一样,Ruby故意避免这种情况。
显然,这就是为什么在Ruby中禁止包含/附加类的原因。