Pharo by Example - 第十七章 类和元类

标签: pharo ; smalltalk ;


在Pharo中,万物皆对象,每个对象都是某个类的实例。类也不例外:类也是对象,而类对象又是其他类的实例。这种模型简洁、简单、优雅且统一,完全捕捉了面向对象编程的精髓。然而,这种统一性的含义可能会让初学者感到困惑。

请注意,你无需完全理解这种统一性的深层含义,也能流畅地在Pharo中编程。然而,本章的目标有两个:(1) 尽可能深入地探讨;(2) 展示这里并没有什么复杂、神奇或特别的东西:只是简单且一致的规则在起作用。通过遵循这些规则,你总能理解为什么情况会是现在这样。

类的规则

Pharo的对象模型基于少量统一应用的概念。为了帮助你回忆,以下是我们在“Pharo对象模型”一章中探讨的对象模型规则:

  • 规则1 一切都是对象
  • 规则2 任何对象都是某个类的实例
  • 规则3 每一个类都有一个超类
  • 规则4 一切皆通过发送消息来实现
  • 规则5 方法查找遵循继承链
  • 规则6 类也是对象,并且遵循同样的规则

规则1的结果是,类也是对象,因此规则2告诉我们,类也必须是某个类的实例。类所属的类称为“元类”。

17.2 元类

当你定义一个新类时,都会自动创建一个元类。大多数时候,你并不需要关心元类。然而,当你使用浏览器浏览类的class side时,想象一下你实际上是在浏览一个不同的类是很有帮助的。类以及元类是两个独立的类。事实上,PointPoint class是不同的,对于类及其元类也是如此。

为了恰当地解释类和元类,我们需要在“Pharo对象模型”一章的规则基础上,补充以下额外规则。

  • 规则7 每一个类都是某个元类的实例
  • 规则8 元类的层次结构与类的层次结构平行
  • 规则9 每一个元类都继承自ClassBehavior
  • 规则10 每一个元类都是Metaclass的实例
  • 规则11 Metaclass的元类也是Metaclass的实例

这11条规则一起构成了Pharo完整的对象模型。

我们将首先通过一个小例子来简要回顾第10章:Pharo对象模型中的第5条规则。然后,我们将使用相同的例子来仔细研究这些新规则。

17.3 重温Pharo对象模型

  • 规则1. 因为所有东西都是对象,所以在Pharo中有序集合(ordered collection)也是一个对象。
OrderedCollection withAll: #(4 5 6 1 2 3)
>>> an OrderedCollection(4 5 6 1 2 3)
  • 规则2. 每个对象都是某个类的实例。例如,一个有序集合(ordered collection)是类 OrderedCollection 的实例:
(OrderedCollection withAll: #(4 5 6 1 2 3)) class
>>> OrderedCollection
  • 规则3. 每个类都有一个超类。OrderedCollection的超类是SequenceableCollectionSequenceableCollection的超类是Collection:
OrderedCollection superclass
>>> SequenceableCollection

SequenceableCollection superclass
>>> Collection

Collection superclass
>>> Object
  • 规则4. 一切都通过发送消息来实现,所以我们可以推断:withAll:是发送给OrderedCollection的消息,而class是发送给OrderedCollection实例的消息,而superclass是发送给OrderedCollectionSequenceableCollection以及Collection类的消息。任何情况下,接收器都是一个对象,因为所有东西都是对象,但这些对象中的某一些也是类。

  • 规则5. 方法查找遵循继承链,因此当我们向 (OrderedCollection withAll: #(4 5 6 1 2 3)) asSortedCollection 的结果发送 class 消息时,消息会在类 Object 中找到对应的方法并处理,如图17-1所示。

17.4 每一个类都是某个元类的实例

正如我们在前面17.2节中提到的,元类的实例本身就是类。这是为了确保我们可以精确地引用Point类以及Point class的类。

元类是隐式的

元类在你定义一个类时会自动创建。我们说它们是隐式的,因为作为程序员,你无需关心它们。每当你创建一个类时,都会为其生成一个隐式的元类,因此每个元类只有一个实例。

普通类是有名字的,而元类是匿名的。但是,我们总是可以通过它们的实例(类)来引用它们。SortedCollection的类为SortedCollection classObject的类为Object class

SortedCollection class
>>> SortedCollection class

Object class
>>> Object class

事实上,元类并不是真正的匿名,它们的名字是从它们的单一实例中推导出来的。

SortedCollection class name
>>> 'SortedCollection class'

Object class name
>>> 'Object class'

图17-2展示了每个类如何是其元类的实例。请注意,由于空间限制,我们在图和解释中省略了 SequenceableCollectionCollection。它们的缺失并不会改变整体的含义。

17.5 查询元类

事实上,类也是对象,这使得我们可以很容易地通过发送消息来查询它们。我们一起来看看:

OrderedCollection subclasses
>>> {SortedCollection . ObjectFinalizerCollection .
     WeakOrderedCollection . OCLiteralList . GLMMultiValue}
     
SortedCollection subclasses
>>> #()

SortedCollection allSuperclasses
>>> an OrderedCollection(OrderedCollection SequenceableCollection
                         Collection Object ProtoObject)
                         
SortedCollection instVarNames
>>> #(#sortBlock)

SortedCollection allInstVarNames
>>> #(#array #firstIndex #lastIndex #sortBlock)

SortedCollection selectors
>>> #(#sortBlock: #add: #groupedBy: #defaultSort:to: #addAll:
      #at:put: #copyEmpty #, #collect: #indexForInserting:
      #insert:before: #reSort #addFirst: #join: #median #flatCollect:
      #sort: #sort:to: #= #sortBlock)

17.6 元类的层次结构与类的层次结构平行

规则7指出,元类的超类不能是任意类:它被限制为元类的唯一实例的超类的元类。例如,SortedCollection 的元类继承自 OrderedCollectionSortedCollection 的超类)的元类。

SortedCollection class superclass
>>> OrderedCollection class

SortedCollection superclass class
>>> OrderedCollection class

这就是我们所说的元类的层次结构与类的层次结构是平行的意思。图17-3显示了如何在SortedCollection层次结构中工作。

SortedCollection class
>>> SortedCollection class

SortedCollection class superclass
>>> OrderedCollection class

SortedCollection class superclass superclass
>>> SequenceableCollection class

SortedCollection class superclass superclass superclass superclass
>>> Object class

17.7 类和对象之间的一致性

退一步来看,将消息发送给对象和发送给类之间并没有什么区别,这是很有趣的。在这两种情况下,对相应方法的查找都是从接收者的类中开始,然后沿着继承链向上查找。

因此,发送给类的消息会遵循元类的继承链。例如,考虑在 Collection 的类端(class side)实现的方法 withAll:。当我们向类 OrderedCollection 发送消息 withAll: 时,它的查找方式与任何其他消息相同。查找从 OrderedCollection class 开始(因为查找从接收者的类开始,而接收者是 OrderedCollection),然后沿着元类继承链向上查找,直到在 Collection class 中找到该方法(见图17-4)。该方法会返回一个 OrderedCollection 的新实例。

OrderedCollection withAll: #(4 5 6 1 2 3)
>>> an OrderedCollection (4 5 6 1 2 3)

只查找一个方法

在Pharo中,只有一种统一的方法查找机制。类只是对象,并且像其他任何对象一样行为。类之所以具有创建新实例的能力,仅仅是因为类能够响应消息 new,而 new 方法知道如何创建新实例。

通常,非类对象不能理解这条消息,但是如果你认为有必要,就没有什么可以阻止你给非元类添加new方法。

17.8 检视对象和类

因为类也是对象,所以我们也可以检视它们。

检视OrderedCollection withAll: #(4 5 6 1 2 3)OrderedCollection

请注意,在一种情况下,你正在检查 OrderedCollection 的实例,而在另一种情况下,你正在检查 OrderedCollection 类本身。这可能会让人有些困惑,因为检查器的标题栏会显示被检查对象的类名。

通过检视OrderedCollection允许你查看OrderedCollection类的超类、实例变量、方法字典等,如图17-5所示:

17.9 每一个元类都继承自ClassBehavior

每个元类都是类的一种(具有单个实例的类),因此从Class继承。反过来,类继承了它的超类,Class,ClassDescriptionBehavior。因为Pharo中的所有东西都是对象,所以这些类最终都继承自Object。我们可以在图17-6中看到完整的图片

new在哪里定义

要理解元类继承自ClassBehavior这一事实的重要性,搞清楚new在哪里定义以及如何查找到它会有所帮助。

当消息new被发送给一个类时,它会在它的元类链中查找,并最终在它的超类Class, ClassDescriptionBehavior中查找,如图17-7所示

当我们向SortedCollection类发送消息new时,将在元类SortedCollection class中查找该消息,并沿着继承链向上查找.记住,它的查找过程与任何别的对象是一样的。

消息new是在哪里被定义的?这个问题至关重要。首先,newBehavior类中定义,并且可以在它的子类中重新定义,包括我们定义的类的任何元类,这是必要的。

现在,当一个消息new被发送给一个类时,它会像往常一样,在这个类的元类中查找,继续沿着超类链向上,直到Behavior类,如果它没有在这个过程中被重新定义过的话。

注意,发送SortedCollection new的结果是一个SortedCollection的实例,而不是Behavior的实例,尽管该方法是在Behavior类中找到的。

方法new总是返回self的一个实例,即接收者的类,即是它是在另一个类(超类)中实现的。

SortedCollection new class
>>> SortedCollection       "并非Behavior"

一个常见错误

一个常见的错误是在接收类的超类中寻找 new 方法。对于 new: 也是如此,这是创建给定大小对象的标准消息。例如,Array new: 4 会创建一个包含4个元素的数组。你不会在 Array 或其任何超类中找到这个方法定义。相反,你应该在 Array class 及其超类中查找,因为查找会从这里开始(见图17-7)。

方法newnew:定义在元类中,因为它们是在响应发送给类的消息时执行的。

此外,由于类是一个对象,它也是在Object上所定义的方法的的接收者。当我们将消息classerror:发送给Point类时,方法查找将遍历元类链(在Point class中查找,在Object class中查找....)一直到Object

17.10 Behavior, ClassDescriptionClass的职责

Behavior

Behavior提供了具有实例的对象所需的最小状态,其中包括超类链接、方法字典和类格式。类格式是一个整数,用于编码指针/非指针的区别、紧凑/非紧凑的区别以及实例的基本大小。Behavior继承自Object,所以它和它的所有子类都可以表现得象Object一样。

Behavior也是编译器的基本接口。它提供了创建方法字典、编译方法、创建实例(比如new,basicNew,new:, basicNew:)、操作类层次结构(如superclass:, addSubclass:)、访问方法(如selector,allSelectors,compiledMethodAt:)、访问实例和变量(如allInstances, instVarNames...)、访问类层次结构(如superclass, subclasses)以及查询(如hasMethods, includesSelector, canUnderstand:, inheritsFrom:, ``isVariable`)。

ClassDescription

ClassDescription 是一个抽象类,为其两个直接子类 ClassMetaclass 提供了必要的功能。ClassDescriptionBehavior 提供的基础上增加了许多功能:命名的实例变量、将方法分类到协议中、维护变更集和记录变更日志,以及大部分用于导出变更的机制。

Class

Class 代表了所有类的共同行为。它提供了类名、编译方法、方法存储和实例变量。它为类变量名和共享池变量提供了具体表示(例如 addClassVarName:addSharedPool:initialize)。由于元类是其唯一实例(即非元类)的类,所有元类最终都继承自 Class(如图17-9所示)。

17.11 每一个元类都是Metaclass的实例

下一个问题是,既然元类也是对象,它们应该是另一个类的实例,但是哪个类呢?如图17-8所示,它们是Metaclass的实例。类Metaclass的实例是匿名类,每个元类只有一个实例,也就是一个类。

Metaclass代表了元类共同的行为。它提供了创建实例(subclassOf:), 创建元类的唯一实例的初始化实例、类变量初始化、元实实例、方法编译和类信息(继承链、实例变量...)的方法。

17.12 Metaclass的元类是一个Metaclass的实例

最后一个要回答的问题是:Metaclass class是什么?

答案很简单:它是一个元类,所以它必须是Metaclass的一个实例,就像系统中所有别的类的元类一样(参见图17-9)

图17-9显示了所有的元类都是Metaclass的实例,包括Metaclass本身的元类。如果比较图17-8和图17-9,你将看到元类层次结构是如何完美地映射类层次结构的,一直到Object类。

以下示例展示了如何查询类层次结构,以证明图17-9是正确的。(实际上,你会发现我们撒了一个小谎——Object class superclass 返回的是 ProtoObject class,而不是 Class。在Pharo中,我们需要再向上一层超类才能到达 Class。)

Collection superclass
>>> Object

Collection class superclass
>>> Object class

Object class superclass superclass
>>> Class

Class superclass
>>> ClassDescription

ClassDescription superclass
>>> Behavior

Behavior superclass
>>> Object

"The class of a metaclass is the class Metaclass"
Collection class class
>>> Metaclass

"The class of a metaclass is the class Metaclass"
Object class class
>>> Metaclass

"The class of a metaclass is the class Metaclass"
Behavior class class
>>> Metaclass

"The class of a metaclass is the class Metaclass"
Metaclass class class
>>> Metaclass

"Metaclass is a special kind of class"
Metaclass superclass
>>> ClassDescription

17.13 本章总结

本章深入研究了Pharo的统一对象模型,并且更全面地解释了类是如何组织的。如果你迷路了或者感到困惑,你应该始终记住消息传递才是关键:你在接收者的类中查找方法。这适用于任何接收者。如果在接收者的类中找不到该方法,则在其超类中查找。

  • 每一个类都是某个元类的实例。元类是隐式的。当你创建一个类时,会自动创建与之相对应的元类。你所创建的类是它唯一的实例。

  • 元类层次结构与类层次结构平行。类的方法查找与普通对象的方法查找平行,并遵循元类的超类链。

  • 每个元类都继承自ClassBehavior。每个类都是一个Class,因为元类也是Class,所以它们也必须从Class继承。Behavior提供了所有具有实例的对象的公共行为。

  • 每个元类都是Metaclass的实例,ClassDescription提供了类和元类通用的所有内容。

  • Metaclass的元类是Metaclass的实例。关系的实例形成了一个闭合的循环,所以Metaclass class class的结果是Metaclass.