第10章面向对象设计•面向对象设计过程与准则•体系结构模块及依赖性•系统分解•问题域部分的设计•人机交互部分的设计•任务管理部分的设计•数据管理部分的设计•对象设计10.1面向对象设计过程与准则•面向对象设计过程(1)建立系统环境模型。在设计的初始阶段,系统设计师用系统环境图对软件与外部实体交互的方式进行建模。下图给出了系统环境图的一般的结构。10.1面向对象设计过程与准则(2)设计系统体系结构。体系结构设计可以自底向上进行,如将关系紧密的对象组织成子系统或层;也可以自顶向下进行,尤其是使用设计模式或遗产系统时,会从子系统的划分入手。(3)对各个子系统进行设计。对于面向对象的系统,典型的子系统有问题域子系统、人机交互子系统和任务管理子系统。(4)对象设计及优化。对象设计以问题领域的对象设计为核心,其结果是一个详细的对象模型。对象设计过程包括使用模式设计对象、接口规格说明、对象模型重构、对象模型优化4组活动。10.1面向对象设计过程与准则•面向对象设计准则(1)模块化传统的面向过程方法中的模块通常是函数、过程及子程序等,而面向对象方法中的模块则是类、对象、接口、构件等。在面向过程的方法中,数据及在数据上的处理是分离的;而在面向对象方法中,数据及其上的处理是封装在一起的,具有更好的独立性,也能够更好地支持复用。10.1面向对象设计过程与准则(2)抽象面向对象方法不仅支持过程抽象,而且支持数据抽象。类实际上就是一种抽象数据类型。可以将类的抽象分为规格说明抽象及参数化抽象。类对外开放的公共接口构成了类的规格说明,即协议。这种接口规定了外部可以使用的服务,使用者无需知道这些服务的具体实现算法。通常将这类抽象称为规格说明抽象。参数化抽象是指当描述类的规格说明时并不具体指定所要操作的数据类型,而是将数据类型作为参数。10.1面向对象设计过程与准则(3)信息隐藏在面向对象方法中,信息隐藏通过对象的封装性实现。对于类的用户来说,属性的表示方法和操作的实现算法都应该是隐藏的。(4)弱耦合耦合是指一个软件结构内不同模块之间互连的紧密程度。在面向对象方法中,对象是最基本的模块,因此,耦合主要指不同对象之间相互关联的紧密程度。10.1面向对象设计过程与准则(5)强内聚•内聚衡量一个模块内各个元素彼此结合的紧密程度。在面向对象设计中存在以下3种内聚:(1)服务内聚:一个服务应该完成一个且仅完成一个功能。(2)类内聚:设计类的原则是,一个类应该只有一个用途,它的属性和服务应该是高内聚的。类的属性和服务应该全都是完成该类对象的任务所必需的,其中不包含无用的属性或服务。如果某个类有多个用途,通常应该把它分解成(3)一般—特殊内聚:设计出的一般—特殊结构,应该符合多数人的概念,更准确地说,这种结构应该是对相应的领域知识的正确抽取。10.1面向对象设计过程与准则(6)可重用•软件重用是提高软件开发生产率和目标系统质量的重要途径。•重用基本上从设计阶段开始。重用有两方面的含义:一是尽量使用已有的类(包括开发环境提供的类库,及以往开发类似系统时创建的类),二是如果确实需要创建新类,则在设计这些新类的协议时,应该考虑将来的可重复使用性。体系结构设计描述了建立计算机系统所需的数据结构和程序构件。一个好的体系结构设计要求软件模块的分层及编程标准的执行。在面向对象软件中,常见的软件模块有类、接口、包和构件。在设计阶段我们往往关注类、接口和包,在实现阶段关注构件,而在部署阶段则关注构件的部署,也就是将构件部署在哪些结点上。10.2体系结构模块及依赖性1.类在面向对象的程序设计中,类和接口是程序的基本组成单元。一个典型程序需要界面类专门负责表示用户界面信息,需要数据库类负责与数据库进行交互,需要有业务逻辑类负责算法计算等。在计算机程序中,要设计和实现的所有类都具有唯一的名字,在不同的阶段或从不同的角度可以将它们称为设计类、实现类、系统类、应用类等。类及其依赖性2.继承依赖性依赖性管理中最棘手的问题是由于继承所引起的依赖性。继承是一种在父类和子类之间共享属性和行为的方式,所以运行时可以用一个子类对象代替其父类对象。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个子类对象是一种特殊的父类对象,它继承父类的所有特征,同时它又可以覆盖父类的方法,从而改变从父类继承的一些特征,并可以在子类中增加一些新的功能。这样,从客户的角度看,在继承树中为请求提供服务的特定对象不同,系统的运行行为可能会有所不同。类及其依赖性(1)多态继承根据为请求提供服务的对象不同可以得到不同的行为,这种现象称为多态。在运行时对类进行实例化,并调用与实例化对象相应的方法,称为动态绑定、后期绑定或运行时绑定。相应地,如果方法的调用是在编译时确定的,则称为是静态绑定、前期绑定或编译时绑定。多态并不是伴随着继承而出现。如果在子类中不覆盖父类中的任何方法,就不会产生多态行为。很明显,继承会带来类和方法之间的依赖性。继承带来的依赖性有编译时继承依赖性和运行时继承依赖性。类及其依赖性类及其依赖性①编译时继承依赖性右图所示的例子说明了一棵树中类之间的编译时依赖性。在这个例子中,B继承A,但没有覆盖A中的方法do1()。因此,B和A之间没有运行时继承依赖性。也就是说,由于编译时依赖性的存在,A中do1()方法的任何变化,都会被B在编译时(静态地)继承。一般来说,所有的继承都会引入编译时依赖性。依赖性是可传递的,也就是说,如果C依赖B,B依赖A,那么C也依赖A。②运行时继承依赖性下图举例说明了在一棵继承树中涉及客户对象访问类服务的运行时继承依赖性。图中类B的do1()方法是从父类A继承来的,因此Test与B没有运行时继承依赖性,只是一个静态依赖性,通过从Test到A的关联来表明。如果在doTest方法中调用的是do2()方法,或者在B中覆盖了A的do1()方法,则从Test到A和B就会存在运行时依赖性。类及其依赖性(2)无多态继承使用继承最简单的方式是子类不覆盖从父类继承来的方法,这样就不存在多态性继承问题。虽然无多态的继承有时并不是十分有用,但理解和管理起来是最容易的。(3)扩展继承和约束继承扩展继承是指子类继承父类的属性,并且提供额外属性来增强类定义。子类是父类的一种,如果子类覆盖了父类的方法,那么被覆盖的方法应该实现该方法的定义,并且能够在子类的语境中工作。当一个类覆盖了继承来的方法,并对一些继承来的功能进行了限制,这时就产生了约束继承。这时,子类不再是父类的一种。有时,限制会造成继承方法的完全禁止。当方法的实现是空时,就会发生这种情况。类及其依赖性3.交互依赖性交互依赖性也称为方法依赖性,是通过消息连接产生的。如下图所示。类及其依赖性图中,CActioner使用方法do1()来发送一条消息do3()给EEmployee,因此,do1()依赖于do3()。依赖性向上传递给所属的类,因此,CActioner依赖于EEmployee。类似地,EOutMessage的do2()调用EEmployee的方法do3(),因此,EOutMessage依赖于EEmployee。类及其依赖性10.3系统分解•子系统和类在大型和复杂的软件系统情形,首先根据需求的功能模型(用例模型),将系统分解成若干个部分,每一部分又可分解为若干子系统或类,每个子系统还可以由更小的子系统或类组成,如图所示。系统结构的类图10.3系统分解•服务和子系统接口服务是一组有公共目的的相关操作。而子系统则通过给其他子系统提供服务来发挥自己的能力。与类不同的是,子系统不要求其他子系统为它提供服务。供其他子系统调用的某个子系统的操作集合就是子系统的接口。子系统的接口包括操作名、操作参数类型及返回值。面向对象的系统设计主要关注每个子系统提供服务的定义,即枚举所有的操作、操作参数和行为。10.3系统分解•服务和子系统接口子系统分层的目的是建立系统的层次结构。每一层仅依赖于它下一层提供的服务,而对它的上一层可以一无所知。下图给出了一个三层的系统结构的示例。10.3系统分解•服务和子系统接口如果在一个系统的层次结构中,每一层只能访问与其相邻的下一层,则称之为封闭体系结构;如果每一层还可访问比其相邻下一层更低的层次,则称之为开放体系结构。典型的封闭体系结构的例子就是开放系统互联参考模型(OSI模型),如图所示。10.3系统分解•服务和子系统接口开放体系结构的一个例子是Java的Swing用户接口包。它允许绕过高层直接访问低层接口以克服性能瓶颈。如图所示。10.3系统分解•Coad&Yourdon的面向对象设计模型Coad&Yourdon基于MVC(Model-View-Controller)模型,在逻辑上将系统划分为4个部分,分别是问题域部分、人机交互部分、任务管理部分及数据管理部分,每一部分又可分为若干子系统。Coad与Yourdon在设计阶段中继续采用了分析阶段中提到的5个层次,用于建立系统的4个组成成分。每一个子系统都由主题、类-&-对象、结构、属性和服务5个层次组成。这5个层次可以被当作整个模型的水平切片。10.3系统分解•典型的面向对象设计模型10.3系统分解•子系统之间的两种交互方式客户-供应商关系:在这种关系中,客户子系统调用供应商子系统,后者完成某些服务工作并返回结果。使用这种交互方案,作为客户的子系统必须了解作为供应商的子系统的接口,而后者却无须了解前者的接口。平等伙伴关系:在这种关系中,每个子系统都可能调用其他子系统,因此每个子系统都必须了解其他子系统的接口。与第一种方案相比,这种方案中,子系统间的交互更加复杂。10.3系统分解•组织系统的两种方案分层组织:这种组织方案把软件系统组织成一个层次系统,每层是一个子系统。上层在下层的基础上建立,下层为实现上层功能而提供必要的服务。每一层内所包含的对象,彼此间相互独立,而处于不同层次上的对象,彼此间往往有关联。块状组织:这种组织方案把软件系统垂直地分解成若干个相对独立的、弱耦合的子系统,一个子系统相当于一块,每块提供一种类型的服务。混合使用层次结构和块状结构,可以成功地由多个子系统组成一个完整的软件系统。10.4问题域部分的设计•典型的面向对象系统一般由三层组成,即数据库层、业务逻辑层及用户界面层。那么,在这三层中,首先从哪一层开始设计呢?•实际上,面向对象的设计也是以面向对象分析的模型为基础的。•面向对象的分析模型包括有用例图、类图、顺序图和包图,主要是对问题领域进行描述,基本上不考虑技术实现,当然也不考虑数据库层和用户界面层。•面向对象分析所得到的问题域模型可以直接应用于系统的问题域部分的设计。所以,面向对象设计应该从问题域部分的设计开始,也就是三层结构的中间层——应用逻辑层。10.4问题域部分的设计•在面向对象设计过程中,可能对面向对象分析所得出的问题域模型做以下方面的补充或调整。(1)调整需求。有两种情况会导致修改通过面向对象分析所确定的系统需求:一是用户需求或外部环境发生变化;二是分析员对问题理解不透彻,导致分析模型不能完整、准确地反映用户的真实需求。10.4问题域部分的设计(2)复用已有的类从类库选择已有的类,从供应商那里购买商业外购构件,从网络、组织、小组或个人那里搜集适用的遗留软构件,把它们增加到问题域部分的设计中去。在被复用的已有类和问题域类之间添加泛化(一般化∕特殊化)关系,继承被复用类或构件属性和方法。标出在问题域类中因继承被复用的已有类或构件而成为多余的属性和服务。修改与问题域类相关的关联。10.4问题域部分的设计(3)把问题域类组合在一起在进行面向对象设计时,通常需要先引入一个类,以便将问题域专用的类组合在一起,它起到“根”类的作用,将全部下层的类组合在一起。当没有一种更满意的组合机制可用时,可以从类库中引进一个根类,作为包容类,把所有与问题领域有关的类关联到一起,建立类的层次。之后,将同一问题领域的一些类集合起来,存于类库中。10.4问题域部分的设计(4)增添泛化类以建立类间的协议有时某些问题