在Pharo中,万物皆对象,每个对象都是某个类的实例。类也不例外:类也是对象,而类对象又是其他类的实例。这种模型简洁、简单、优雅且统一,完全捕捉了面向对象编程的精髓。然而,这种统一性的含义可能会让初学者感到困惑。
请注意,你无需完全理解这种统一性的深层含义,也能流畅地在Pharo中编程。然而,本章的目标有两个:(1) 尽可能深入地探讨;(2) 展示这里并没有什么复杂、神奇或特别的东西:只是简单且一致的规则在起作用。通过遵循这些规则,你总能理解为什么情况会是现在这样。
类的规则
Pharo的对象模型基于少量统一应用的概念。为了帮助你回忆,以下是我们在“Pharo对象模型”一章中探讨的对象模型规则:
- 规则1 一切都是对象
- 规则2 任何对象都是某个类的实例
- 规则3 每一个类都有一个超类
- 规则4 一切皆通过发送消息来实现
- 规则5 方法查找遵循继承链
- 规则6 类也是对象,并且遵循同样的规则
规则1的结果是,类也是对象,因此规则2告诉我们,类也必须是某个类的实例。类所属的类称为“元类”。
17.2 元类
当你定义一个新类时,都会自动创建一个元类。大多数时候,你并不需要关心元类。然而,当你使用浏览器浏览类的class side
时,想象一下你实际上是在浏览一个不同的类是很有帮助的。类以及元类是两个独立的类。事实上,Point
与Point class
是不同的,对于类及其元类也是如此。
为了恰当地解释类和元类,我们需要在“Pharo对象模型”一章的规则基础上,补充以下额外规则。
- 规则7 每一个类都是某个元类的实例
- 规则8 元类的层次结构与类的层次结构平行
- 规则9 每一个元类都继承自
Class
和Behavior
- 规则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
的超类是SequenceableCollection
,SequenceableCollection
的超类是Collection
:
OrderedCollection superclass
>>> SequenceableCollection
SequenceableCollection superclass
>>> Collection
Collection superclass
>>> Object
规则4. 一切都通过发送消息来实现,所以我们可以推断:
withAll:
是发送给OrderedCollection
的消息,而class
是发送给OrderedCollection
实例的消息,而superclass
是发送给OrderedCollection
和SequenceableCollection
以及Collection
类的消息。任何情况下,接收器都是一个对象,因为所有东西都是对象,但这些对象中的某一些也是类。规则5. 方法查找遵循继承链,因此当我们向
(OrderedCollection withAll: #(4 5 6 1 2 3)) asSortedCollection
的结果发送class
消息时,消息会在类Object
中找到对应的方法并处理,如图17-1所示。
17.4 每一个类都是某个元类的实例
正如我们在前面17.2节中提到的,元类的实例本身就是类。这是为了确保我们可以精确地引用Point
类以及Point class
的类。
元类是隐式的
元类在你定义一个类时会自动创建。我们说它们是隐式的,因为作为程序员,你无需关心它们。每当你创建一个类时,都会为其生成一个隐式的元类,因此每个元类只有一个实例。
普通类是有名字的,而元类是匿名的。但是,我们总是可以通过它们的实例(类)来引用它们。SortedCollection
的类为SortedCollection class
,Object
的类为Object class
:
SortedCollection class
>>> SortedCollection class
Object class
>>> Object class
事实上,元类并不是真正的匿名,它们的名字是从它们的单一实例中推导出来的。
SortedCollection class name
>>> 'SortedCollection class'
Object class name
>>> 'Object class'
图17-2展示了每个类如何是其元类的实例。请注意,由于空间限制,我们在图和解释中省略了 SequenceableCollection
和 Collection
。它们的缺失并不会改变整体的含义。
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
的元类继承自 OrderedCollection
(SortedCollection
的超类)的元类。
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 每一个元类都继承自Class
和Behavior
每个元类都是类的一种(具有单个实例的类),因此从Class
继承。反过来,类继承了它的超类,Class
,ClassDescription
和Behavior
。因为Pharo中的所有东西都是对象,所以这些类最终都继承自Object
。我们可以在图17-6中看到完整的图片
new
在哪里定义
要理解元类继承自Class
和Behavior
这一事实的重要性,搞清楚new
在哪里定义以及如何查找到它会有所帮助。
当消息new
被发送给一个类时,它会在它的元类链中查找,并最终在它的超类Class
, ClassDescription
和Behavior
中查找,如图17-7所示
当我们向SortedCollection
类发送消息new
时,将在元类SortedCollection class
中查找该消息,并沿着继承链向上查找.记住,它的查找过程与任何别的对象是一样的。
消息new
是在哪里被定义的?这个问题至关重要。首先,new
在Behavior
类中定义,并且可以在它的子类中重新定义,包括我们定义的类的任何元类,这是必要的。
现在,当一个消息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)。
方法new
和new:
定义在元类中,因为它们是在响应发送给类的消息时执行的。
此外,由于类是一个对象,它也是在Object
上所定义的方法的的接收者。当我们将消息class
或error:
发送给Point
类时,方法查找将遍历元类链(在Point class
中查找,在Object class
中查找....)一直到Object
。
17.10 Behavior
, ClassDescription
和Class
的职责
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
是一个抽象类,为其两个直接子类 Class
和 Metaclass
提供了必要的功能。ClassDescription
在 Behavior
提供的基础上增加了许多功能:命名的实例变量、将方法分类到协议中、维护变更集和记录变更日志,以及大部分用于导出变更的机制。
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的统一对象模型,并且更全面地解释了类是如何组织的。如果你迷路了或者感到困惑,你应该始终记住消息传递才是关键:你在接收者的类中查找方法。这适用于任何接收者。如果在接收者的类中找不到该方法,则在其超类中查找。
每一个类都是某个元类的实例。元类是隐式的。当你创建一个类时,会自动创建与之相对应的元类。你所创建的类是它唯一的实例。
元类层次结构与类层次结构平行。类的方法查找与普通对象的方法查找平行,并遵循元类的超类链。
每个元类都继承自
Class
和Behavior
。每个类都是一个Class
,因为元类也是Class
,所以它们也必须从Class
继承。Behavior
提供了所有具有实例的对象的公共行为。每个元类都是
Metaclass
的实例,ClassDescription
提供了类和元类通用的所有内容。Metaclass
的元类是Metaclass
的实例。关系的实例形成了一个闭合的循环,所以Metaclass class class
的结果是Metaclass
.