2.2 对象模型基础

结构化的设计方法指导开发者利用算法作为基本构建块来构建复杂系统。类似地,面向对象设计方法利用类和对象作为基本构建块,指导开发者探索基于对象和面向对象编程语言的表现力。

实际上,对象模型受到了一些因素的影响,而不只是面向对象程序设计。实际上,正如补充材料“基础-对象模型”中进一步讨论的,对象模型已被证实是计算机科学中的一个统一的概念,它不仅仅适用于程序设计语言,也适用于用户界面、数据库甚至计算机架构的设计。这种广泛适用的原因就是,面向对象帮助我们处理许多种不同系统中固有的复杂性。

因此,面向对象分析和设计代表了一种演进式的开发,而不是一种革命性的开发。它没有抛弃过去的优点,而是基于已经证明的好方法。遗憾的是,大多数程序员没有在OOAD上接受严格的培训。确实,许多优秀的工程师利用结构化设计技术开发并部署了无数有用的软件系统。然而,只利用算法分解在处理大量复杂性的情况时是有局限的,所以我们必须转向面向对象分解。而且,如果试图将C++和Java这样的语言当作传统的面向算法语言来使用,那么我们不仅会丧失语言提供的威力,而且结果还常常不如使用C和Pascal这样的老式语言。如果一个木匠不懂电,给他一个电钻,他也只会把它当作锤子来用。他最后可能会弄弯一些钉子,弄伤几根手指,因为电钻不是一把好锤子。

由于面向对象模型有许多不同的来源,所以它不幸地具有一些让人糊涂的术语。Smalltalk程序员使用“方法”,C++程序员使用“虚成员函数”,CLOS程序员使用“通用函数”。Object Pascal程序员说“类型强制”,Ada程序员则称之为“类型转换”,C#或Java程序员会使用“cast”。为了减少混淆,让我们来定义什么是面向对象,什么不是面向对象。

“面向对象”这个短语“已经被毫无顾虑地滥用了,就像‘故乡’、‘苹果派’、‘结构化程序设计’一样。”[17]我们都同意,对象是面向对象的核心。在第1章中,我们将对象非正式地定义为一个可触摸的实体,它展示了某些定义良好的行为。Stefik和Bobrow将对象定义为“包含了属性、过程和数据的实体,它们执行计算并保存局部的状态”[8]。将对象定义为实体在某种程度上有点问题,但这里的基本概念是对象统一了算法抽象和数据抽象的思想。Jones进一步澄清了这个术语,指出“在对象模型中,重点在于灵活地刻画物理系统或抽象系统的组件,用一个程序系统来建模……对象具有某种‘完整性’,这种完整性不应违反,实际上也不能违反。对象只能够按照适合它的方式来改变状态、改变行为、实现操作或与其他对象发生联系。换言之,存在一些不变的特征,这些特征刻画了一个对象和它的行为。以一个电梯为例,刻画它的不变特征包括它只能在其竖井中上下运动……所有对电梯的模拟都必须包含这些不变特征,因为它们与电梯的概念是不可分割的整体”[32]

基础-对象模型

Yonezawa和Tokoro指出,“术语‘对象’在计算机科学的不同领域中独立地出现,几乎都是在20世纪70年代初期,它们出现时指的是不同的概念,但互相有关。所有这些概念的发明都是为了管理软件系统的复杂性,即对象代表了按模块分解的系统的组件,或者是知识表达的模块化单元”[9]。Levy补充指出,下列事件促使了面向对象概念的演进。

■ 计算机架构的进步,包括基于功能的系统以及对操作系统概念的硬件支持;

■ 程序设计语言的进步,如Simula、Smalltalk、CLU和Ada;

■ 编程方法学的进步,包括模块化和信息隐藏[10]

我们可以加上如下三点,它们也为对象模型的基础做出了贡献:

■ 数据库模型的进步;

■ 对人工智能的研究;

■ 哲学和认知科学领域的进步。

对象的概念最先出现在20多年前的硬件领域中,始于基于描述符的架构(descriptor-based architecture)的发明,以及稍后的基于功能的架构(capability-based architecture)[11]。这些架构代表了与经典的冯·诺依曼架构的决裂,尝试拉近编程语言的高级抽象和机器本身的低级抽象之间的距离[12]。据它的支持者说,这种架构的好处很多:更好的错误检测、改进的执行效率、更少的指令类型、更简单的编译,以及减少的存储需求。计算机也可以拥有面向对象的架构。

与面向对象架构密切相关的是面向对象操作系统。Dijkstra在THE多程序系统(THE multiprogramming system)中的工作首次引入了将系统构建成分层状态机的概念[18]。其他的先锋面向对象操作系统包括Plessey/System 250(为Plessey 250台处理器设计)、Hydra(为CMU的C.mmp设计)、CALTSS(为CDC 6400设计)、CAP(为Cambridge CAP计算机设计),UCLA Secure UNIX(为PDP 11/45和11/70设计)、StarOS(为CMU的Cm*设计)、Medusa(同样为CMU的Cm*设计)和iMAX(为Intel 432设计)[19]

对面向对象模型最重要的贡献也许来自于一些程序设计语言,我们称为基于对象或面向对象的程序设计语言。类和对象的基本思想首次出现在程序设计语言Simula 67中。Flex系统及后续的各种Smalltalk方言,如Smalltalk-72、Smalltalk-74、Smalltalk-76和最终现在使用的版本Smalltalk-80,均采用了Simula的面向对象设计方式,很自然地将语言中的所有东西都变成了对象的实例。在20世纪70年代,像Alphard、CLU、Euclid、Gypsy、Mesa和Modula这样的语言被设计出来,它们支持后来兴起的数据抽象的要领。语言研究导致了将Simula和Smalltalk的概念嫁接到传统的高级程序设计语言上。面向对象概念和C的结合产生了C++和Objective C。然后出现了Java,帮助程序员避免使用C++中经常发生的编程错误。在Pascal中加入面向对象机制导致产生了Object Pascal语言、Eiffel和Ada。另外,许多Lisp的方言也包含了Simula和Smalltalk的面向对象特征。附录A将更详细地讨论一些这样的语言和其他程序设计语言的开发。

第一个正式指出按照层抽象来分解系统的重要性的人是Dijkstra。Parnas后来引入了信息隐藏的思想[20],在20世纪70年代,一些研究者在抽象数据类型机制的研究上走在了前面,最著名的有Liskov and Zilles [21]、Guttag [22]和Shaw [23]。Hoare提出了关于类型和子类的理论。

虽然数据库技术的发展在某种程度上独立于软件工程,但它也为对象模型做出了贡献[25],主要是通过实体-关系(ER)的方式进行数据建模[26]。ER模型由Chen[27]首先提出,在ER模型中,世界的模型由实体、实体的属性以及实体之间的关系构成。

在人工智能领域,知识表示方面的进步对理解面向对象的抽象也做出了贡献。在1975年,Minsky首先提出了一个理论框架,将真实世界对象表示为观察到的图像以及自然语言认知系统[28]。从那时起,框架就在不同的人工智能系统中成为了架构的基础。

最后,哲学和认知科学对面向对象的发展也做出了贡献。世界可以被看作是对象或过程,这两种观点是希腊人的发明,在17世纪,笛卡儿说人们很自然地用面向对象的观点来看世界[29]。在20世纪,Rand在她的客观主义认知论哲学中对这些主题进行了扩展。最近,Minsky提出了一个人类智能的模型。在这个模型中,他认为意识是由一些无意识的代理构成的群体[31]。Minsky指出,只有通过这些代理的协作,我们才能发现所谓的“智能”。

2.2.1 面向对象编程

那么,究竟什么是面向对象编程(OOP)?我们这样定义:

“面向对象编程是一种实现的方法,在这种方法中,程序被组织成许多组相互协作的对象,每个对象代表某个类的一个实例,而类则属于一个通过继承关系形成的层次结构。”

这个定义有三个要点:(1)利用对象作为面向对象编程的基本逻辑构建块(第1章中介绍的“组成部分”层次结构),而不是利用算法;(2)每个对象都是某个类的一个实例;(3)类与类之间可以通过继承关系联系在一起(第1章中介绍的“是一个”层次结构)。一个程序可能看起来像是面向对象的,但是如果不满足这三点之一,它就不是一个面向对象的程序。具体来说,没有继承的编程显然不是面向对象的,那只是利用抽象数据类型在编程。

根据这个定义,某些语言是面向对象的,而另一些语言就不是。Stroustrup建议,“如果‘面向对象语言’这个术语有意义的话,它一定是意味着一种语言具有一些机制,能很好地支持面向对象风格的编程……如果语言提供的机制能够使得利用一种风格编程很方便,那么这种语言就很好地支持了这种风格。如果使用一种语言需要额外的努力或技能才能编写这样的程序,那它就不支持这种技术。在这种情况下,这种语言只是让开发者能用这些技术”[33]。从理论的角度来说,人们可以在Pascal甚至COBOL或汇编这样的非面向对象语言中模拟面向对象编程,但这样做非常得不偿失。所以Cardelli和Wegner说:

“当且仅当一种语言满足下列需求时,它才是面向对象的。

■ 它支持对象,这些对象是具有命名的操作接口和隐藏的内部状态的数据抽象;

■ 对象有相关的类型[类];

■ 类型[类]可以从超类型[超类]中继承属性。”[34]

一种语言支持继承,就意味着它可以表达类型之间的“是一种”关系。例如,红玫瑰是一种花,而花是一种植物。如果一种语言不提供对继承的直接支持,那么它就不是面向对象的。Cardelli和Wegner将这种语言称为“基于对象(object-based)”的,而不是“面向对象(object-oriented)”的。在这个定义之下,Smalltalk、Object Pascal、C++、Eiffel、CLOS、C#和Java都是面向对象的,Ada83是基于对象的(后来在Ada95中加入了面向对象的支持)。但是,因为对象和类是这两种语言的基本元素,所以在基于对象和面向对象的语言中,都可以使用面向对象设计,而且也是非常推荐的。

2.2.2 面向对象设计

编程方法中的重点主要是正确有效地使用特定的语言机制,而设计方法的重点是正确有效地构造出复杂系统的结构。那么,究竟什么是面向对象设计(OOD)?我们建议采用下面的定义:

“面向对象设计是一种设计方法,包括面向对象分解的过程和一种表示法,这种表示法用于展现被设计系统的逻辑模型和物理模型、静态模型和动态模型。”

这个定义有两个要点:(1)面向对象设计导致了面向对象分解;(2)面向对象设计使用了不同的表示法,来表达系统逻辑设计(类和对象结构)和物理设计(模块和处理架构)的不同模型,以及系统的静态和动态特征。

支持面向对象分解,是面向对象设计与结构化设计的不同之处:前者利用类和对象抽象来构建逻辑系统结构,后者则利用算法抽象。我们用术语“面向对象设计”来指所有导致面向对象分解的方法。

2.2.3 面向对象分析

对象模型甚至对软件开发生命周期的更早阶段也会产生影响。传统的结构化分析技术关注系统中的数据流,DeMarco [35]、Yourdon [36]、Gane和Sarson [37]的著作是最好的代表,Ward和Mellor [38]以及Hatley和Pirbhai [39]对其进行了实时扩展。面向对象分析(OOA)的重点在于构建真实世界的模型,利用面向对象的观点来看世界:

“面向对象分析是一种分析方法,这种方法利用从问题域的词汇表中找到的类和对象来分析需求。”

OOA、OOD和OOP之间的关系如何?基本上,面向对象分析的结果可以作为开始面向对象设计的模型,面向对象设计的结果可以作为蓝图,利用面向对象编程方法最终实现一个系统。