为什么不能将类用作模块?

时间:2018-11-23 15:57:33

标签: ruby class module superclass

ModuleClass的超类:

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原则的原因是什么?为什么不能将类用作模块?

2 个答案:

答案 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类的祖先图:

enter image description here

这会增加复杂性和歧义性,并引发"the diamond problem"

  • D的实例是否共享通过B继承并通过C继承的类A中的实例变量?
  • 如果他们不分享他们,
    我们需要扩展的语法来指定
    我们是否要通过B或C访问A中的实例变量。

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]

包含/前置模块仅使祖先链保持简单链。
没有坏事发生。


包含/添加课程

现在假设IncludeMePrependMeIntermediate不是模块而是类。

为简单起见,我将只上一堂课:

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中禁止包含/附加类的原因。