面向对象的设计概述OOA和OOD之间有密切的衔接关系,从OOA到OOD是一个逐渐扩充模型的过程。分析处理以问题为中心,可以不考虑任何与特定计算机有关的问题,而OOD则把我们带进了面向计算机的“实地”开发活动中去。通常,OOD分为两个阶段,即高层设计和低层设计。高层设计建立应用的体系结构。低层设计集中于类的详细设计。(1)高层设计高层设计阶段开发软件的体系结构,构造软件的总体模型。在这个阶段,标识在计算机环境中进行问题解决工作所需要的概念,并增加了一批需要的类。这些类包括那些可使应用软件与系统的外部世界交互的类。此阶段的输出是适合应用软件要求的类、类间的关系、应用的子系统视图规格说明。通常,利用面向对象设计得到的系统框架如图6.11所示。①高层设计模型一个典型的高层设计模型即客户-服务器模型,它构造起应用软件的总体模型,这个模型导出的体系结构既可在过程性系统中使用,又可在面向对象的系统中使用。客户-服务器模型的想法是让系统的一个部分(服务器子系统)提供一组服务给系统的另一个部分(客户子系统)。请求服务的对象都归于客户子系统,而接受请求提供服务的部分就是服务器。图6.11OOD设计导出的体系结构②高层设计的规则最小化各构件间的通信:在子系统的各个高层构件之间的通信量应当达到最小。一个用户界面应当能够自行处理交互、错误改正和硬件控制,而不需打扰主应用。隐藏复杂性:子系统应当把那些成组的类打包,形成高度的内聚。逻辑功能分组:虽然输入和输出设备可能相互间不通信,但逻辑上把它们归组到一个处理输入/输出的子系统中。这样比较容易识别并定位问题论域中的事件。类与通过概念封装的子系统十分类似。事实上,每个子系统都可以被当做一个类来实现,这个类聚集它的构件,提供了一组操作。类和子系统的结构是正交的,一个单个类的实例可能是不止一个子系统的一部分。高层设计阶段增加了一批必要的类,主要包括了那些可使应用软件与系统的外部世界交互的类。这些交互则包括与其它软件系统(如数据库管理系统、鼠标和键盘)的界面,与使用来进行数据收集或者负责控制的硬件设备的界面等。高层设计可以表征为标识和定义模块的过程。但这种模块可以是单个的类,还可以是由一些类组合成的子系统。定义过程是职责驱动的。高层设计和类设计这两个阶段是相对封闭的。在这种情况下,应用软件中的每一个事物都是一个对象,包括应用软件自身在内!根据这个思想,这两个阶段又是连接的。应用软件的设计是大类的设计,这种类设计考察应用软件所期望的每一个行为,并利用这些行为形成应用类的界面。(2)类设计的目标和方针类设计的第一步是标识应用所需的概念。应用分析过程包括了对问题论域所需的类的模型化;但在最终实现应用时不只有这些类,还需要追加一些类。类设计的主要目标如下:①单一概念的模型:在分析与高层设计阶段,常常需要使用多个类来表示一个“概念”。一般人们在使用面向对象方法开发软件时,常常把一个概念进行分解,用一组类来表示这个概念。当然,也可以只用一个独立的类来表示一个概念。②可复用的构件:我们希望所开发的构件可以在未来的应用中使用。因此,需要一些附加特性。例如,在相关的类的集合中界面的标准化③可靠的构件:应用软件必须是可靠的(健壮的和正确定义的)软件。而这种可靠性与它的构件有关。每个构件必须经过充分的测试。但由于成本关系,往往测试不够完备。然而,如果我们要建立可复用的类,则通过测试确保构件的可靠性是绝对必要的。④可集成的构件:我们希望把类的实例用到其它类的开发和应用中,这要求类的界面应当尽可能小,一个类所需要的数据和操作都定义在类定义中。因此,类的设计应当尽量减少命名冲突。面向对象语言的消息语法可通过鉴别带有实例名的操作名来减少可能的命名冲突。类结构提供的封装使得把概念集成到应用的工作变得很容易。封装特性保证了把一个概念的所有细节都组合在一个界面下,而信息隐蔽则保证了实现级的名字将不会其它类的名字互相干扰。我们讨论的方针是类的模块设计的方针,还要给出类设计质量的度量。①信息隐蔽:软件设计通过信息隐蔽可增强抽象,并可保护类的存储表示不被抽象数据类型实例的用户直接存取。对其表示的唯一存取途径只能是界面。②消息限制:类的设计者应当为类的命令设计一个明确的界面,该类实例的用户应当只使用界面提供的操作。③狭窄界面:不是所有的操作都是公共的。只有对其它类的实例是必要的操作才放到界面上,其它操作应是隐蔽实现的部分。④强内聚:模块内部各个部分之间应有较强的关系⑤弱耦合:一个单独模块应尽量不依赖于其它模块。如果在类A的实例中建立了类B的实例,或者如果类A的操作需要类B的实例做为参数,或者如果类A是类B的一个派生类,则称类A“依赖于”类B。一个类应当尽可能少地依赖于其它类。耦合程度部分依赖于所使用的分解方法。类A之所以依赖于类B,是因为类A要求类B提供服务。这个依赖性可通过复制类A中的类B的功能来消除。但代码的复制减少了系统的灵活性并增加了维护的困难。继承结构损害了弱耦合的概念。因为在建立一般化-特殊化关系的时候,继承引入了依赖。⑥显式信息传递:除了依赖于最少的类外,还应该明确在这些类之间的信息流。在类之间全局变量的共享隐含了信息的传递,并且是一种依赖形式。因此,两个类之间的交互应当仅涉及显式信息传递。显式信息传递是通过参数表来完成的。⑦派生类当做派生类型:继承结构的使用是面向对象开发方法的一大特色。每个派生类应该当做基类的特殊化来开发,而基类所具有的公共界面成为派生类的共有界面的一个子集。C++允许设计者选择类的基类是共有的或私有的。如果基类是共有的,则其共有界面将成为新的派生类的共有界面部分,这表明基类的行为成为派生类的行为部分。这类似于类型与派生类型之间的关系。如果基类是私有的,它的行为将不是继承类的公共行为部分而是实现部分。它的提出是为了提供实现新类的服务。⑧抽象类:某些语言提供了一个类,用它做为继承结构的开始点,所有用户定义的类都直接或间接以这个类为基类。Smalltalk提供了一个类Object做为所有类的继承树的根,而C++则支持多重继承结构。每一种结构都包含了一组类,它们是(或应该是)某种概念的特殊化。这个概念应抽象地由结构的根类来表示。因此,每个继承结构的根类应当是目标概念的一个抽象模型。这个抽象模型生成一个类,它不用于产生实例。它定义了一个最小的共有界面,许多派生类可以加到这个界面上以给出概念的一个特定视图。(3)通过复用设计类利用既存类来设计类,有4种方式:选择,分解,配置和演变。这是面向对象技术的一个重要优点。许多类的设计都是基于既存类的复用。①选择:设计类最简单的方法是从既存构件中简单地选择合乎需要的构件。这就是开发软件库的目的。一个OO开发环境应提供常用构件库,大多数语言环境都带有一个原始构件库(如整数、实数和字符),它是基础层。任一基本构件库(如“基本数据结构”构件)都应建立在这些原始层上。这些都是些一般的可复用的类。这个层还包括一组提供其它应用论域服务的一般类,如窗口系统和图形图元。表6.1显示了建立在这些层上面的特定域的库。最低层的论域库包括了应用论域的基础概念并支持广泛的应用开发。特定项目和特定组的库包括一些论域库,它包含为相应层所定义的信息。图6.13建立子类表6.1一个面向对象构件库的层次特定组的构件─一个小组为他们自己组内所有成员使用而开发特定项目的构件─一个小组为某一个项目而开发特定问题论域的构件─购自某一个特定论域的软件销售商一般构件─购自专门提供构件的销售商特定语言原操作─购自一个编译器的销售商②分解:最初标识的“类”常常是几个概念的组合。在设计时,可能会发现所标识的操作落在分散的几个概念中,或者会发现,数据属性被分开放到模型中拆散概念形成的几个组内。这样我们必须把一个类分成几个类,希望新标识的类容易实现,或者它们已经存在。③配置:在设计类时,可能会要求由既存类的实例提供类的某些特性。通过把相应类的实例声明为新类的属性来配置新类。例如,一种仿真服务器可能要求使用一个计时器来跟踪服务时间。设计者不必开发在这个行为中所需的数据和操作,而是应当找到计时器类,并在服务器类的定义中声明它。④演变:要开发的新类可能与一个既存类非常类似,但不完全相同。此时,不适宜采用“选择”操作,但可以从一个既存类演变成一个新类,可以利用继承机制来表示一般化-特殊化的关系。特殊化处理有三种可能的方式。由既存类建立子类:现要建立一个新类“起重车”。它的许多属性和服务都在既存类“汽车”中。关系如图6.13所示。新类是既存类的特殊情形。这时直接让“起重车”类作为“汽车”类的子类即可。②建立继承层次由既存类建立新类:现要增加一个新类“拖拉机”。它的属性与服务有的与“汽车”类相同,有的与“汽车”类不同。关系如图6.14所示。这时,调整继承结构。建立一个新的一般的“车辆”类,把“拖拉机”与“汽车”类的共性放到“车辆”类中,“拖拉机”与“汽车”类都成为“车辆”类的子类。“车辆”是抽象类,相关操作到子类“汽车”类去找。图6.14调整继承结构③建立既存类的父类:另一种情形是想在既存类的基础上加入新类,使得新类成为既存类的一般类。例如,已经存在“三角形”类,“四边形”类,想加入一个“多边形”类,并使之成为“三角形”和“四边形”类的一般类。继承结构如图6.15所示。从这个“多边形”类又可派生出新的类,如“六边形”类。汽车类拖拉机类车辆汽车拖拉机汽车类起重车类图6.15建立一般类后两种涉及既存类的修改。在这两种情况下,既存类中定义的操作或数据被移到新类中。如果遵循信息隐蔽和数据抽象的原理,这种移动应不影响已有的使用这些类的应用。类的界面保持一致,虽然某些操作是通过继承而不是通过类的定义伸到这个类的。(4)类设计方法通常,类中的实例具有相同的属性和操作,应当建立一个机制来表示类中实例的数据表示、操作定义和引用过程。这时,类的设计是由数据模型化、功能定义和ADT定义混合而成的。类是某些概念的一个数据模型,类的属性就是模型中的数据域,类的操作就是数据模型允许的操作。要明确规定它们两个谁先确定是不可能的,两个处理是互补的。类的标识有主动和被动之分。被动类是数据为中心的,它们是根据系统的其它对象发送来的消息而修改其封装数据的;主动类则提供许多系统必须履行的基本操作。与被动类的实例(被动对象)一样,主动类的实例(主动对象)接收消息,但这些对象是负责发送追加消息和控制某些应用部分的。在窗口环境,一个窗口是一个被动对象,它基于发送给窗口的消息来显示某些内容。窗口管理器是一个主动对象,它担负着各种在它控制的窗口上的操作。在被动类与主动类的设计之间不存在明显的差别。在设计主动类时,需要优先确定数据模型,稍后再确定操作;在设计被动类时,把类提供的服务翻译成操作。在标识了服务之后再设计为支持服务所需要的数据。许多类都是这两个极端的混合。类中对象的组成包括了私有数据结构、共享界面操作和私有操作。而消息则通过界面,执行控制和过程性命令。因此,要分别讨论它们的实现。类的设计描述包括两部分:①协议描述:协议描述定义了每个类可以接收的消息,建立一个类的界面。协议描述由一组消息及对每个消息的相应注释组成。②实现描述:实现描述说明了每个操作的实现细节,这些操作应包含在类的消息中。实现描述由以下信息构成:类名和对一个类引用的规格说明私有数据结构的规格说明,包括数据项和其类型的指示每个操作的过程描述实现描述必须包含充足的信息,以提供在协议描述中所描述的所有消息的适当处理。由一个类所提供服务的用户必须熟悉执行服务的协议,即定义“什么”被描述;而服务的提供者(对象类本身)必须关心:服务如何提供给用户,即实现细节的封装问题。四边形类多边形类多边形四边形三角形六边形三角形类人人医学教授人7.Coad与Yourdon面向对象设计方法OOD模型类似于构造蓝图,以最完整的形式全面地定义了如何用特定的实现技术建立起一个目标系统。在OOA模型和OOD模型中使用了共同的表示法。这有助于从分析到设计的转换,并有助于在当前的设计和实现中维护OOA模型。与OOA模型一样,OOD模型也有5层结构,又