1.4 面向对象设计初步人们把设计视为定义系统的构造蓝图、约定和规则,以指导系统的实现。面向对象方法的一个特点是OOA和OOD自然地过渡和结合。但仍然有区别:OOA与系统的问题论域更加相关,OOD与系统的实现更加密切。
本节简要介绍面向对象设计(OOD)的基本要略。
1.4.1 OOD模型
一、从面向对象分析到面向对象设计
1、OOA与OOD的比较
从面向对象分析到面向对象设计是一个逐渐扩充模型的过程。面向对象分析主要模拟问题域和系统任务,而面向对象设计是面向对象分析的扩充,主要是增加各种组成部分。
具体来说,面向对象分析识别和定义类-&-对象,这些类-&-对象直接反映问题域和系统任务。而面向对象设计识别和定义其它附加类-&-对象,它们反映需求的一种实现。面向对象分析和面向对象设计是开发系统时的两个不同的阶段。
多层次 多组成部分模型
多层次 多组成部分模型由5层组成,在设计期间主要扩充为4个部分。
(1)人机交互部分:有效的人机交互所必需的实际显示和输入。设计一组有关类接口视图的用户模型的类和对象,设计用户界面;
(2)问题域部分:面向对象分析的结果放在问题域部分。在该部分中,需要管理面向对象分析的某些类-&-对象、结构、属性和方法的组合与分解。原因可能是时间与空间的折中、内存管理、开发人员的变更及类的调整等。设计构造一组为底层应用建立模型的类和对象,细化分析结果;
(3)任务管理部分:任务定义、通讯和协调,也包括硬件分配、外部系统以及装置协议。即确定系统资源的分配,设计用于系统中类的行为控制的对象/类;
(4)数据管理部分:对永久性数据的访问和管理。即确定系统持久对象的存储,将对象转换成数据库记录或表格;
详细地确定对象和类是OOD的关键工作。一种有效的启发式方法是对需要提供的服务和问题陈述作语法分析,其中名词和名词短语可作候选对象,动词是候选对象服务,形容词表示了可能的子类关系。设计经验和技巧是非常重要的。
在分析和设计中,要注意遵循这样的原则:把构造由基本对象组装成复杂对象或活动对象的过程与分解大粒度对象使系统细化过程相结合;把抽象化与具体化结合起来,把独立封装与继承关系结合起来等。
二、面向对象设计
1、面向对象设计范式
设计范式是用其分解过程的观点来刻划的。过程范式采用面向任务的观点。当提出一种解决目标问题时,所提出的解决方法是通过将其分解成一系列任务来完成的,这些任务形成了过程应用程序的基本结构。分析阶段开发的信息可以作为设计阶段的输入部分,但它是以不同于设计阶段的术语表示的。由于这种原因,传统的软件生命周期观点包含了分析和设计之间的不必要的界限。导致了从分析阶段的问题域到设计阶段的求解决域的不一致。
面向对象的设计范式采用建模的观点。在传统的软件生命周期中,分析和设计同开发问题域的模型紧密相关。而在面向对象的生命周期中,保留了明显的独立结构。人们通过把问题域作为一系列相互作用的实体可以构造出模型。实体的基于软件模型以及模型间的关系汇集成应用程序的基本结构。这样,在分析阶段开发的信息就成为设计阶段的一个主要部分。这种极其自然的过渡是利用了“构件”的一致性。这种一致性与结构化分析和结构化设计有着完全不同的观点。
由过程设计范式产生的构件是执行任务的过程,且与所提出的解决方法有关。
由面向对象范式产生的构件是实体描述,即类。许多这样的描述可直接地反映原始问题,尽管许多类并不表示物理对象,但它们是概念上的实体,可以用问题域的术语来描述。
在传统语言中,程序是由传递参数的过程或函数的集合组成,每个过程处理它的参数,并有可能返回一个值,因此执行单元(即过程)为中心。
在面向对象的语言中,世界被看作独立的对象的集合,相互之间通过过程(通常称作消息)进行通信。对象是主动的,而过程(消息)是被动的,过程(消息)和它们的参数一起被从一个对象传递到另一个对象。一个对象根据提交给它的请求(即过程过又称做方法)并基于这些过程(方法)进行行动,因此以数据(即对象)为中心。由于抽象数据类型普遍地被应用于模拟应用领域内的实体,它们为程序的模块化提供了一个自然的基础,其面向对象程序的机构可以非常相似于应用领域中的结构。
为了说明过程范式和面向对象范式的不同,下面以一个十字路口控制交通灯的软件系统为例子,其简要的需求说明如下:
硬件包括一组传感器、交通灯和控制箱。软件读出传感器的状态,决定十字路口的新状态并且发出改变交通灯的信号。另外,系统还有其他能力,如通过配置集测试循环灯的任选项。系统也可以被设置成缺省状态。传感器指明在特殊车道上有无车辆。有几种类型的传感器,每种传感器的内部工作方式都不同。然而所有的传感器都是中断驱动的,且一旦触发,就置传感器状态位。当决定要改变十字路口的状态时,需要重新设置每个传感器。这个特殊的十字路口用两种类型的交通灯:一种是常见的三色灯,它用于直接向前车道;另一种是四方位灯:红、黄、绿和转动箭头标志。每个灯都有当前状态、下一个状态和缺省状态。控制器的硬件包括灯的开关、传感器的数据存储以及进行定时查询状态的时钟。控制器的软件读时钟,定时查询传感器,确定下一个状态并且发信号改变交通灯。
过程范式通过考虑必须执行的一组任务来开发交通控制系统。如下图所示。层次结构中的每一层表示了更详细的功能。图中任务的顺序是从左到右的。
面向对象范式通过识别问题的实体来开发系统。本例涉及到的物理实体包括传感器、交通灯的控制器。注意,范式并不仅限于物理实体。如本例可识别的非物理实体是“lane”(车道)实体,它把传感器与特定的交通灯联系起来。“lane”实体沟通了问题域和求解域之间的关系。
这些实体的抽象将成为系统的基本成分类,类的规格说明用来补充传统的需求说明的信息。面向对象的需求说明用对象和分类把传统的系统功能需求与面向对象的操纵的对象描述融合在一起,它描述了问题域的一部分。
上面识别的实体以及其他一些实体,如下图所示。这种图用实体关系图来表示实体和实体间的关系,称这种图是问题的语义数据模型。它允许表示更宽范围的信息。
比较上面两图中的信息,可说明两种范式观点上的不同。在过程抽象时,过程范式必须考虑设计过程中数据表示所必须的结构。同样地,面向对象范式在设计中需要操纵一个对象的操作符时,可能采用功能分解法。
在构造面向对象的系统,分析阶段的工作与设计阶段的工作联系的更加紧密,这是因为有公共的对象模型。在分析阶段,分析者确定了问题域对象;而在设计阶段,设计者为特定的基于计算机的解规定了额外的必要的对象,有关实现级的对象还需重复设计过程。
2、实体和实体间的关系面向对象的设计首先涉及到的是实体。实体可以是现实中的对象,如交通灯、椅子、飞机,也可以是抽象概念,如作用、相互影响或事故(飞行)。
实体间的关系指发生在问题域中的对象间的相互作用。飞行员“驾驶”飞机,“驾驶”就是飞行员和飞机之间的一种关系。这种关系是应用级并且用问题域术语来表示的。两个实体之间的关系可能不止一种。一个实体可能与其它的几个实体有关系。
面向对象范式的一种重要设计关系是继承关系。如为了描述类pilot(飞行员),设计者可能希望从类person(人)的描述开始,利用继承机制可把person的所有行为和属性都加到pilot的定义中去。
面向对象范式的另一种设计关系是成分关系。有关person的描述可能包含名字属性,通过声明一个字符串类实例就可在person中定义这个属性。
继承关系和成分关系之间的区别是:继承关系表示现有定义的特化,如飞行员和人。成分关系用于在类的实现中提供的一种服务设施,如人与字符串。
完整的数据模型
面向对象的设计包括类设计和应用设计两个部分,并且被融合在应用开发中。类设计可以隐含在应用设计中,而应用设计包括确定问题域中的各种实体以及实现求解的一些特定实体。每个实体的类型都引出类描述,一旦开发出概念上完整的类描述,就可以设计出应用系统。通过连接类实例(对现实世界建模),利用它们相互间的作用,从而产生问题的解。
类描述包括三部分:属性定义、类接口描述和类实例的可能状态之间的有效变换集。下面考虑设计栈的例子,如下图所示。图(a)说明栈的数据属性,包括栈中的若干数据存储和栈操作方法;图(b)给出栈的公共接口;图(c)状态图说明栈的有效状态序列。
类实例间相互作用的模式确定了应用系统的结构。这些相互作用是类与类之间的关系模型。可以用几种方法为这些关系建模,但它们都应包括消息在内。传递消息可以是同步的,也可以是异步的,这依赖于执行模式的选择。
语义数据模型提供了支持开发应用程序的完整的数据模型,这种应用程序包含有高级应用关系和低级类定义关系。下图给出了上面所述的飞行员/飞机应用片段的实体关系图。
这样,开发一个面向对象应用系统就成为组合类描述和应用系统结构。许多过程都是由现实世界的问题域来指导的,而不是用我们求解问题的观点。这就形成在高层包含问题域模型的应用设计。应用设计过程从顶层开始,经过识别类到低层,然后基于低层定义来设计低层类,再进行向上设计。
类的设计准则
在任何面向对象的应用中,类实例是系统的主要组成部分,而且如果采用纯面向对象的方法,那么整个系统就是由类实例组成的。因此,每个独立的类的设计对整个应用系统都有影响。下面简单介绍进行类的设计时要考虑的因素:
(1)类的公共接口的单独成员应该是类的操作符;
(2)类A的实例不应该直接发送消息给类B的成分;
(3)操作符是公共的当且仅当类实例的用户可用;
(4)属于类的每个操作符要么访问要么修改类的每个数据;
(5)类必须尽可能少地依赖其他类;
(6)两个类之间的相互作用应该是显式的;
(7)采用子类继承超类的公共接口,开发子类成为超类的特化;
(8)继承结构的根类应该是目标概念的抽象模型。
前4条准则着重讲述类接口的适当形式和使用。
准则1要求的信息隐蔽增强了开发表示独立的设计。
准则2进一步说明了这种封装性,它禁止访问用作为类的部分表示的类实例。这些准则都强调了一个类是由其操作集来刻划的,而不是其表达的思想。
准则3把公共接口定义为在类表示中包含了全部的公共操作集。
准则4要求属于类的每个操作符都必须表示其建模的概念行为。
这4条准则为设计者指明了开发,分解类接口以及类表示的方向。
后4条准则着重考虑类之间的关系。
准则5要求设计者尽可能少地连接一个类与其它类。如果一个正被设计的类需要另一个类的许多设施的话,也许这种功能应表示成一个新类。
准则6试图减少或者消除全局信息。一个类所需要的任何信息都应该从另一个类中用参数显式地传递给它。
准则7禁止使用继承性开发新类的公共接口之外的部分。利用类实例作为另一个类的部分表示的最佳方法是,在新设计的类表示中声明支持类实例。
准则8鼓励设计者开发类的继承结构,这种类是抽象的特化。这些抽象导致了更多的可重用子类,并确定了子类之间的不同。
1.4.2 什么是优良的OOD
一个优良的OOD应具备的基本条件:
(1)类与类的继承必须具有高度凝集性;
(2)类与类之间的耦合应该很松散。只有一个例外,具有类的继承关系必须是紧密联系的,因而子类与父类要紧密耦合;
(3)某个类的数据实现细节对于别的类来说应该是隐藏的;
(4)设计应该具有最优的可重用性;
(5)尽力使类、对象和方法的定义具有简单性;
(6)对所设计的类和类族,应注意保持其协议或接口的稳定性;
(7)类的层次结构设计规模适度,不要太深或太浅;
(8)系统整体规模要最小化。
1.4.3 对象标识设计
对象标识的目的是明确地区别对象,要求在一系统中,对象标识具有唯一性、稳定性和一致性。
在对象/类的标识方法中,有几种实用的设计方式:
(1)以间接地址标识对象。如在Smalltalk80 中,就是以对象指针指向一个对象表作对象标识,这种方法支持数据的独立性。
(2)以结构化标识符标识对象。这类似于C++的变量命名,而且隐式类/对象的层次及指示的逻辑范围。
(3)以代名词标识对象。在DBMS中广泛采用这种使用关键字的做法,易于记忆,但也易于非唯一、非连续。
(4)以内部编号标识对象。
(5)类似于指针的另一种对象标识是引用。引用将一个新的标识符与对象联系起来,这样可以在程序中根据需要为对象重新命名。
如果将地址与标识相混淆,对对象的永久存储和共享将得不到保证。C++的对象标识方法在实现上是使用对象的存储地址,在设计时要注意采取避免发生问题的措施。
使用对象标识时,系统区别对象的同一和等值是把具有相同标识的对象视为同一个对象,而值相等的对象则应具有不同的标识符。
1.4.4 复杂对象的构造设计
复杂对象模型的结构设计时,要注意以对象本身的自然表达方式为出发点,将对象按照结构层次、功能性质和操作行为划分成不同的类,建立所有类的有向无环图。
一般地讲,复杂对象具有多种数据结构,可分解为多层次低层对象,或者不同层次的部件,每个部件对象又可参与其他对象的构成,为多方共享,还可以按照各种特性定义,相应地归入不同的类。概念设计的目的在于定义抽象对象的关系结构,常用的方法如下所述。
1、分类
分类是依据共同的行为将对象进行分组。如何选择模拟分类是设计中的一个焦点,这要同系统的总体规划设计一起统筹安排。
2、概括
概括是从某些具有共性的对象或类中提出抽象出高一层次的类。反之,由高层次类可以衍生出低层次的对象或子类。它们之间是“is-a”关系,即高层是低层的泛化,底层是高层的特化(实例)。
3,聚集
聚集是从有联系的成分对象构造的抽象对象,表示一个对象可从结构上划分为多个部分,是“拥有”或“由…组成”联系,即整体-部分关系。这种结构划分可以逐层细化,形成类的层次结构。
值得注意的是,在刚开始学习进行面向对象设计时,往往容易把类的层次设计得过深,这会带来在实现上的不少困难和其它问题,应有意识地力求避免发生这种情况。
1.4.5 一个GIS的OOD模型实例
以一个多媒体地理信息系统的模型为例,从感性上进一步理解上述概念和原则。
地理信息系统(GIS:geography information system)是一个对地理信息进行存储、管理、分析、加工的复杂计算机应用系统。GIS处理的数据信息涉及大量的图文表示和空间要素分析。传统的方法已无法适应,利用面向对象设计方法可以对GIS要素加以合理的抽象,并且这种抽象在概念上是自然的、简洁的、易于理解的。
将地形要素进行抽象,反映到计算机中来,实际的地形环境是数字地形模型。地形要素的变换即是地形对象的操作(方法描述),地形要素的可量度性即是地形对象的属性,地形要素即是地形对象。根据地形学的分类原则及应用要求,可将地形环境中各种地形要素分类抽象为测量点、障碍物、道路、铁路、河流、湖泊等不同的类,再根据点、线、面等特征进行类的层次划分,从而构成一个分层结构的面向对象模型。图1.6是该分析的表示。