第六章 面向对象的软件工程 一、复习要求 1. 了解面向对象的概念,包括什么是范型,面向对象的概念,对象和对象的分类等。 2. 了解用面向对象方法构造软件的开发过程,包括应用生存期和类生存期的概念。 3. 了解面向对象分析方法,包括论域分析,应用分析的介绍。 4. 了解面向对象设计方法,包括高层设计模型和设计原则,类设计的目标和方针,复用设计及类设计的方法。 5. 了解有影响的coad方法、Booch方法和OMT方法的基本思想。 二、内容提要 面向对象技术是一个非常实用而强有力的软件开发方法。它的特征是: ( 方法的唯一性,即方法是对软件开发过程所有阶段进行综合考虑而得到的。 ( 从生存期的一个阶段到下一个阶段的高度连续性,即生存期后一阶段的成果只是在前一阶段成果的补充和修改。 ( 把面向对象分析(OOA)、面向对象设计(OOD)和面向对象程序设计(OOP)集成到生存期的相应阶段。 1. 面向对象的概念 (1) 范型 范型(Paradigm)又称为范例、风范或模式(Pattern)。从软件开发角度来看,范型与问题解决技术有关。范型定义了特定的问题和应用的开发过程中将要遵循的步骤,确定将用于表示问题和它的解决的那些成分的类型,并利用这些成分表示与问题解决有关的抽象,直接得到问题的结构。因此,范型的选择影响整个软件开发生存期。就是说,它支配了设计方法、编码语言、测试和检验技术的选择。 ① 流行的范型 :目前流行多种范型,它们提供了许多方法,可进行系统分解。流行的范型有:过程性的,逻辑的,面向存取的,面向进程的,面向对象的,函数型的,说明性的。每个范型都有它的支持者和用户,每个范型都特别适合于某种类型的问题或子问题。例如,逻辑程序设计范型是基于规则的,它把有关问题的知识分解成一组具体规则,用语言的“if_then”等结构来表示这些规则。面向存取范型是一种在构造用户界面方面很有用的技术。此外,每一个范型都用不同的方式考虑问题,每一个范型都使用不同的方法来分解问题,而且每一个范型都导致不同种类的块、过程、产生规则。下面主要讨论三种范型。研究的目的是帮助我们找到解决问题的入手点。 ② 过程性范型 :过程性范型是使用最广泛、历史最长的软件范型。它产生过程的抽象,这些抽象把软件视为处理流,并定义成由一系列步骤构成的算法。每一步骤都是带有预定输入和特定输出的一个过程,把这些步骤串联在一起可产生合理的稳定的贯通于整个程序的控制流,最终产生一个简单的具有静态结构的体系结构,如图6.1(a)所示。 过程性范型侧重建立构成问题解决的处理流,数据抽象、数据结构是根据算法步骤的要求开发的,它贯穿于过程,提供过程所要求操作的信息。系统的状态是一组全局变量,这组全局变量保存状态的值,把它们从一个过程传送到另一个过程。 过程性范型是一种成熟的应用开发过程。对这种方法已有许多支持工具。然而,在大型系统的开发上存在一些问题。 ③ 面向对象范型 :在过程性范型中优先考虑的是过程抽象,而在面向对象范型中优先考虑的是实体,即问题论域的对象。在面向对象范型中,把标识和模型化问题论域中的主要实体做为系统开发的起点,主要考虑对象的行为而不是必须执行的一系列动作。 面向对象系统中的对象是数据抽象与过程抽象的综合。系统的状态保存在各个数据抽象的核心所定义的数据存储中。控制流包含在各个数据抽象中的操作内。不像在过程性范型里那样,把数据从一个过程传送到另一个过程,而是把控制流从一个数据抽象通过消息传送到另一个数据抽象。完成的系统体系结构更复杂但也更灵活,如图6.2(b)所示。把控制流分离成块,这样可以把复杂的动作视为各个局部间的相互作用。  图6.1 过程性系统和面向对象系统的基本构造 ④ 面向进程的范型 :面向进程的范型是把一个问题分解成独立执行的模块。让不只一个程序同时运行。这些进程互相配合,解决问题。面向进程范型产生的主要的块是进程。一个进程中的活动独立于其它进程的活动,但可以要求从其它进程得到信息,或为其它进程提供信息。甚至可以异步处理,仅需要进程暂停发送或接收信息。在面向对象范型中,各个对象是相对独立的,但也存在单线索(单线程)控制。面向进程范型支持与面向对象范型相同的封装,但可提供多线索(多线程)执行。 ⑤ 混合范型 :在大型系统的开发中,很难说哪种范型对整个问题的解决最好。系统开发现在有一种补充步骤,可把大型问题分解成一组子问题。对于每个子问题可以采用适当的软件范型。例如,设计一个智能数据分析系统时,可把它分解为4个子系统,如图6.2所示。系统的数据库界面,可以使用面向对象的方法进行设计;智能数据分析用逻辑范型设计;而分析算法则是过程性的;系统通过一个用户界面来实用化,这个用户界面是用面向存取范型设计出来的。 这种设计需要有某种实现语言或一组协同语言的支持。许多流行的功能不断增强的语言支持不只一种设计范型。对于混合范型,现在已经存在不少技术。像C++和并发C这样的语言都是多范型语言,支持过程性范型和面向对象范型。并发C还支持面向进程范型。系统可以使用单一的语言,利用两种或多种范型写成。还可以利用可共享数据格式和连接规约的某些语言,把用这些语言分别编写的块链接到某个单一的应用中去。 (2) 面向对象的概念 关于“面向对象”,有许多不同的看法。Coad和Yourdon给出了一个定义:“面向对象 = 对象 + 类 + 继承 + 消息通信”。如果一个软件系统是使用这样4个概念设计和实现的,则认为这个软件系统是面向对象的。一个面向对象的程序的每一成份应是对象,计算是通过新的对象的建立和对象之间的消息通信来执行的。 (3) 对象(object) 一般意义来讲,对象是现实世界中存在的一个事物。可以是物理的,如一个家具或桌子,如图6.3所示,可以是概念上的,如一个开发项目。对象是构成现实世界的一个独立的单位,具有自己的静态特征(用数据描述)和动态特征(行为或具有的功能)。 例如:人的特征:姓名、性别、年龄等,行为:衣、食、住、行等。 ① 对象、属性、操作、消息定义 对象可以定义为系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位,由一组属性和一组对属性进行操作的服务组成。 属性一般只能通过执行对象的操作来改变。 操作又称为方法或服务,在C++中称为成员函数,它描述了对象执行的功能,若通过消息传递,还可以为其它对象使用。 而所谓的消息是一个对象与另一个对象的通信单元,是要求某个对象执行类中定义的某个操作的规格说明。发送给一个对象的消息定义了一个操作名和一个参数表(可能是空的),并指定某一个对象。由一个对象接收的消息则调用消息中指定的操作,并将传递过来的实际参数与参数表中相应的形式参数结合起来。接收对象对消息的处理可能会改变对象中的状态,即改变接收对象的属性,并发送一个消息给自己或另一个对象。可以认为,这种消息的传递大致等价于过程性范型中的函数调用。 ② 对象的分类 ( 外部实体:与软件系统交换信息的外部设备、相关子系统、操作员或用户等。 ( 信息结构:问题信息域中的概念实体,如信号、报表、显示信息等。 ( 需要记忆的事件:在系统运行过程中可能产生并需要系统记忆的事件,如单击鼠标左键、击打键盘“(”键等。 ( 角色:与软件系统交互的人员所扮演的角色,如经理、部长、技术支持等。 ( 组织机构:有关机构,如单位、小组等。 ( 位置:作为系统环境或问题上下文的场所、位置,如客户地址、收件人(机构)地址等。 ( 操作规程:如操作菜单、某种数据输入过程等。 在标识对象时必需注意遵循“信息隐蔽”的原则:必需将对象的属性隐藏在对象的内部,使得从对象的外部看不到对象的信息是如何定义的,只能通过该对象界面上的操作来使用这些信息。对象的状态通过给对象赋予具体的属性值而得到。它只能通过该对象的操作来改变。 对象有两个视图,分别表现在分析设计和实现方面。从分析及设计方面来看,对象表示了一种概念,它们把有关的现实世界的实体模型化。从实现方面来看,一个对象表示了在应用程序中出现的实体的实际数据结构。之所以有两个视图,是为了把说明与实现分离,对数据结构和相关操作的实现进行封装。 (4) 类(class)和实例(instance) 把具有相同特征和行为的对象归在一起就形成了类。类成为某些对象的模板,抽象地描述了属于该类的全部对象的属性和操作。属于某个类的对象叫做该类的实例。对象的状态则包含在它的实例变量,即实例的属性中。如图6.4所示。从“李杰”、“王辉”和“杨芳”等对象可得到类“学生”,而这些对象就称为该类的实例。 类定义了各个实例所共有的结构,类的每一个实例都可以使用类中定义的操作。实例的当前状态是由实例所执行的操作定义的。 图6.4 对象、类与实例 面向对象程序设计语言,如C++和 smalltalk都定义了一个new操作,可建立一个类的新实例。C++还引入了构造函数,用它在声明一个对象时建立实例。此外,程序设计语言给出了不同的方法,来撤消(称为析构)实例,即当某些对象不再使用时把它们删去,把存储释放以备其它对象使用。C++给出了一个操作delete,可以释放一个对象所用的空间。C++还允许每个类定义自己的析构方法,在撤消一个对象时调用它。smalltalk没有提供一个机制来撤消对象,但可以进行无用单元收集。 类常常可看做是一个抽象数据类型(ADT)的实现。但更重要的是把类看做是表示某种概念的一个模型。事实上,类是单个的语义单元,它可以很自然地管理系统中的对象,匹配数据定义与操作。类加进了操作,给通常的记录赋予了语义,可提供各种级别的可访问性。 (5) 继承 (inheritance) 如果某几个类之间具有共性的东西(信息结构和行为),抽取出来放在一个一般类中,而将各个类的特有的东西放在特殊类中分别描述,则可建立起特殊类对一般类的继承。如图6.5所示,。各个特殊类可以从一般类中继承共性,这样避免了重复。 图6.5 特殊类对一般类的继承关系 建立继承结构的好处: ( 易编程、易理解 代码短, 结构清晰; ( 易修改:共同部分只要在一处修改即可; ( 易增加新类:只须描述不同部分。 (6) 多继承 如果一个类需要用到多个既存类的特征,可以从多个类中继承,称为多继承。例如退休教师是继承退休者和教师这两个类的某些特征或行为而得到的一个新类。 (7) 多态性和动态绑定 对象互相通信,即一个对象发消息给另一个对象,执行某些行为或又发消息给另外的对象,从而执行系统的功能。 发送消息的对象可能不知道另一个对象的类型是什么。如在C程序中使用命令ClearInt ( ) 时要严格区分该命令适合一个整数,还是一个整数数组。但在C++情形,ClearInt ( ) 对两者都适用,它自己判断对象是哪一个。 这就是多态性。它意味着一个操作在不同类中可以有不同的实现方式。如清零操作 ClearInt ( ) 针对消息对象是 int array 还是int,其实现是不同的。在一个面向对象的多态性语言中,可能代替一个特定类型的类型的集合就是它的子类集合。 例如,图6.7给出了 4 个类的继承层次。使用这个继承结构,发送给多边形类的所有消息,它的所有子类都能够响应。又例如,想要在屏幕上画一系列多边形,多态性允许一个表的元素可以属于一组指定的类型而不仅仅是一个类型,可以认为这是一个类族。通过遍历这个表,发送给各个表元素以draw消息,画出所有的多边形。 动态绑定把函数调用与目标代码块的连接延迟到运行时进行。这样,只有发送消息时才与接收消息实例的一个操作绑定。它与多态性可以使我们建立的系统更灵活,易于扩充。做为动态绑定的例子,考虑在多边形类中的方法contains? (aPoint)。这个操作可以在类层次的各层重新实现,以有效利用各个子类的特殊的特征。例如,假定一个矩形有某些边与屏幕的边平行,这时,检查一个点是否包含在矩形内,比检查一个点是否在一个一般的四边形内的效率要高一些。 2. 面向对象软件的开发过程 面向对象范型不仅是一些具体的软件开发技术与策略,而且是一整套关于如何看待软件系统与现实世界的关系以及如何进行系统构造的软件方法学。用面向对象开发方法构造的软件具有以下特点: ( 面向对象的技术建立的模型与客观世界一致,因而便于理解; ( 适应变化的需要,修改局限在模块中; ( 可复用性。 (1) 应用生存期 图6.8给出应用生存期模型。在图中各个阶段的顺序是线性的,但实际上开发过程不是线性的。还没有办法用图来逼真地反映在面向对象开发过程中各个阶段之间的复杂交互。有一部分分析工作在设计之前实行,但有些分析工作与其它部分的设计与实现并行进行。  图6.8 一个基于复用的应用生存期 开发可复用的软件构件是软件开发过程的一部分。面向对象方法以类作为单元,并分别考虑类的生存期与应用生存期。类生存期可包含在图6.8中的类开发阶段中,可与应用生存期集成。 (2) 类生存期 在面向对象软件开发过程中特别重视复用。软件构件应独立于当初开发它们的应用而存在。构件的开发瞄准某些局部的设计和实现,它们可用于当前问题的解决,但为了在以后的项目中使用,它们还应当足够通用。在以后的应用开发中,可以调整这些独立构件以适应新问题的需要。因此,应使得类成为一个可复用的单元,图6.9提出了一个类生存期。 类生存期与应用生存期交叉。在应用生存期的每一个阶段都可做类的标识。类生存期有自己的步骤,与任一特定应用的开发无关。按照这些步骤,可以完整地描述一个基本实体。而不仅仅考虑当前正在开发的系统。系统开发的各个阶段都可能会标识新的类。随着各个新类的标识,类生存期引导开发工作逐个阶段循序渐进。 例如,在应用分析中已经标识了对一个图形显示设备的要求。如果这样一个图形显示设备类不存在,就应着手开发。但是,用到显示器所有可能操作的应用寥寥无几。若把这些操作的开发当做一个特定应用系统开发的一部分,那么只可能标识和实现该系统所要求的那些操作。但如果考虑让构件独立于应用,就必须能够综合出超出当前系统需求的开发要求,生成一种能表示成一个完全的概念的模型并可建立为以后其它系统复用的类。 在纯面向对象的系统开发中,一个应用程序就“是”一个类。基本的类,像list类,可不涉及应用,但基本类的实例要聚合到其它类的定义中。这些类依次又聚合到更复杂的类定义中,最终将会遇到一个类,它涉及整个应用。 下面概括了类生存期各个阶段主要做的事情。 ① 类的规格说明 :对每一个类都要开发它的规格说明,无论是在哪一个阶段标识的类都是如此。类的规格说明定义了施加于对象的数据存储上的一组操作。这组操作应工作在封装在对象内部的数据存储上,或返回关于对象状态的信息。操作的名字应能反映这个操作本身的含义。类的规格说明必须足够完整,使得它能够与在类资源库中的那些可复用的类的规格说明做比较。 ② 类的设计与实现 :此时尽可能利用既存类提供为当前应用所需要的功能。图6.9给出了利用既存类的三个途径: ( 原封不动地复用既存类。 ( 对既存类进行演化以得到满足要求的类。演化可以是横向的,也可以是纵向的。横向的演化生成既存类的一个新的版本,而纵向的演化将从既存类导出新类。 ( 重新开始进行开发。 一个新的继承结构将建立两种类:一种是抽象类,它概括了将要表达的概念;另一种是具体类,它要实现这个概念。 ③ 求精和维护 :维护活动是针对应用系统的,但求精过程是针对类和结构的。因为我们利用抽象进行开发,因此,维护活动每时每刻都可能修改这些抽象。随着经验的增长,还可以标识抽象的抽象,使得继承结构通过一般化,增加新的层次。 为便于类的调整,应尽量做到定义与实现分离,实现概念封装和信息隐蔽,使得类具有更大的独立性。在使用一个类或复用一个类时,类与类之间产生一种相互依赖关系。但对一个类的公有界面所做的多次修改不应影响使用它的那些类,在公有界面上增加新的操作不应改变既存的软件。需要谨慎处理的是删除操作或改变操作的特征。 (3) 面向对象软件的开发过程 面向对象软件的开发过程开始于问题论域,经历从问题提出到解决的一系列过程。下面具体说明在过程中的这些步骤。 ① 分析阶段 :分析阶段包括两个步骤:论域分析和应用分析。它们都要标识问题论域中的抽象。在分析中,需要找到特定对象,基于对象的公共特性把它们组合成集合,标识出对这个问题的一个抽象。同时要标识抽象之间的关系,并建立对象之间的消息连接。 ( 论域分析 :论域分析开发问题论域的模型。论域分析应当在应用分析之前进行,我们在了解问题之前应当对问题敞开思想考虑,考察问题论域内的一个较宽的范围,分析覆盖的范围应比直接要解决的问题更多。 ( 应用分析 :应用(或系统)分析细化在论域分析阶段所开发出来的信息,并且把注意力集中于当前要解决的问题。因为通过论域分析,分析人员具有了较宽的论域知识,因而能开发出更好的抽象。 ② 高层设计 :在一个纯面向对象环境中,软件体系结构设计与类设计常常是同样的过程,但还是应当把体系结构设计与类的设计分开。在高层设计阶段,设计应用系统的顶层视图。这相当于开发一个代表系统的类,通过建立该类的一个实例并发送一个消息给它来完成系统的“执行”。 ③ 类的开发 :根据高层设计所标识的对各个类的要求和类的规格说明,进行类的开发。因为一个应用系统往往是一个类的继承层次。对这些类的开发是最基本的设计活动。 ④ 实例的建立:建立各个对象的实例,实现问题的解决方案。 ⑤ 组装测试:按照类与类之间的关系组装一个完整的应用系统的过程中进行的测试。各个类的封装和类测试的完备性可减少组装测试所需要的时间。 ⑥ 维护:维护的要求将影响应用和各个类。继承关系可支持对现有应用的扩充,或者加入新的行为,或者改变某些行为的工作方式。 ( 应用系统的维护 :包括在系统的操作中定位故障、在既存的系统中加入新的行为。应用的维护能够简化对类实例的定位、修改其类的实现、通过改变消息或接收消息的次序来改变应用中特殊对象的角色。新的行为可通过定义新的类和建立实例来实现。 ( 类的维护 :把类的实现与其规格说明分离可局部化修改的影响。一般情况下,修正问题要求应尽可能不改变类的界面。然而,为了在系统中增加新的行为,偶尔会有改变界面的需求。 3. 面向对象分析(OOA)与模型化 面向对象分析过程分为论域分析和应用分析。论域分析建立大致的系统实现环境,应用分析则根据特定应用的需求进行论域分析。 (1) OOA分析的基本原则和任务 为建立分析模型,要运用如下的5个基本原则:① 建立信息域模型;② 描述功能;③ 表达行为;④ 划分功能、数据、行为模型,揭示更多的细节;⑤ 用早期的模型描述问题的实质,用后期的模型给出实现的细节。这些原则形成OOA的基础。 OOA的目的是定义所有与待解决问题相关的类(包括类的操作和属性、类与类之间的关系以及它们表现出的行为)。为此,OOA需完成的任务是: ① 软件工程师和用户必须充分沟通,以了解基本的用户需求; ② 必须标识类(即定义其属性和操作); ③ 必须定义类的层次; ④ 应当表达对象与对象之间的关系(即对象的连接); ⑤ 必须模型化对象的行为; ⑥ 反复地做任务①―⑤,直到模型建成。 (2) OOA概述 目前已经衍生许多种OOA方法。每种方法都有各自的进行产品或系统分析的过程,有一组可描述过程演进的图形标识,以及能使得软件工程师以一致的方式建立模型的符号体系。现在广泛使用的OOA方法有以下几种: ① Booch方法 :Booch方法包含“微开发过程”和“宏开发过程”。微开发过程定义了一组任务,并在宏开发过程的每一步骤中反复使用它们,以维持演进途径。Booch OOA宏开发过程的任务包括标识类和对象、标识类和对象的语义、定义类与对象间的关系,以及进行一系列求精从而实现分析模型。 ② Rumbaugh方法 :Rumbaugh和他的同事提出的对象模型化技术(OMT)用于分析、系统设计和对象级设计。分析活动建立三个模型:对象模型(描述对象、类、层次和关系),动态模型(描述对象和系统的行为),功能模型(类似于高层的DFD,描述穿越系统的信息流)。 ③ Coad和Yourdon方法 :Coad和Yourdong方法常常被认为是最容易学习的OOA方法。建模符号相当简单,而且开发分析模型的导引直接明了。其OOA过程概述如下: ( 使用“要找什么”准则标识对象; ( 定义对象之间的一般化∕特殊化结构; ( 定义对象之间的整体∕部分结构; ( 标识主题(系统构件的表示); ( 定义属性及对象之间的实例连接; ( 定义服务及对象之间的消息连接。 ④ Jacobson方法 :也称为OOSE(面向对象软件工程)。Jacobson方法与其它方法的不同之处在于他特别强调使用实例(use case)——用以描述用户与系统之间如何交互的场景。Jacobson方法概述如下: ( 标识系统的用户和它们的整体责任; ( 通过定义参与者及其职责、使用实例、对象和关系的初步视图,建立需求模型; ( 通过标识界面对象、建立界面对象的结构视图、表示对象行为、分离出每个对象的子系统和模型,建立分析模型。 ⑤ Wirfs―Brock方法 :Wirfs―Brock方法不明确区分分析和设计任务。从评估客户规格说明到设计完成,是一个连续的过程。与Wirfs―Brock分析有关的任务概述如下: ( 评估客户规格说明; ( 使用语法分析从规格说明中提取候选类; ( 将类分组以表示超类; ( 定义每一个类的职责; ( 将职责赋予每个类; ( 标识类之间的关系; ( 基于职责定义类之间的协作; ( 建立类的层次表示; ( 构造系统的协作图。 ⑥ 统一的OOA方法(UML) 统一的建模语言(UML)已经在企业中广泛使用,它把Booch、Rumbaugh和Jacobson等各自独立的OOA和OOD方法中最优秀的特色组合成一个统一的方法。UML允许软件工程师使用由一组语法的语义的实用的规则支配的符号来表示分析模型。 在UML中用5种不同的视图来表示一个系统,这些视图从不同的侧面描述系统。每一个视图由一组图形来定义。这些视图概述如下: ( 用户模型视图 :这个视图从用户(在UML中叫做参与者)角度来表示系统。它用使用实例(use case)来建立模型,并用它来描述来自终端用户方面的可用的场景。 ( 结构模型视图 :从系统内部来看数据和功能性。即对静态结构(类、对象和关系)模型化。 ( 行为模型视图 :这种视图表示了系统动态和行为。它还描述了在用户模型视图和结构模型视图中所描述的各种结构元素之间的交互和协作。 ( 实现模型视图 :将系统的结构和行为表达成为易于转换为实现的方式。 ( 环境模型视图 :表示系统实现环境的结构和行为。 通常,UML分析建模的注意力放在系统的用户模型和结构模型视图,而UML设计建模则定位在行为模型、实现模型和环境模型。 (3) 论域分析(Domain Analysis) 论域分析是基于特定应用论域,标识、分析、定义可复用于应用论域内多个项目的公共需求的技术。它的目标是发现和创建一组应用广泛的类,这组类常常超出特定应用的范围,可以复用于其它系统的开发。 论域分析可以被视为软件过程的一种保护伞活动,是与任一软件项目都没有牵连的软件工程活动。论域分析员的工作是设计和建造可复用的构件,供许多工作在类似的但不一定相同的应用项目中的人员使用。 论域分析过程的关键输入∕输出参看图6.10。主要的过程活动有: 图6.10 论域分析的输入∕输出 ① 定义要研究的论域 :分析员首先隔离感兴趣的业务论域、系统类型或产品分类,再抽取OO项和非OO项。其中,OO项包括既存应用的类的规格说明、设计和代码;支持类(GUI类或数据库存取类);与论域相关的市售构件库;测试用例等。非OO项包括方针、步骤、计划、标准和指南;既存的非OO应用的规格说明、设计和测试信息;度量、市售非OO软件等。 ② 分类从论域抽取的项 :对所有的项进行归类并定义各个种类的一般定义特征。提出种类的分类模式并定义每个项的命名惯例。适当的时候建立分类的层次。 ③ 收集论域中各个应用的有代表性的样例 :为了完成这个活动,必须保证在问题中的应用具有适合已定义的某些种类的项。 ④ 分析样例中的每一个应用 :分析员接下来要做的事情是: ( 标识候选的可复用的对象; ( 指明标识对象为可复用的理由; ( 定义可复用对象的适合性; ( 估计在论域中可做到对象复用的应用的百分比; ( 用名字标识对象并使用配置管理技术控制它们。一旦标识了对象,分析员应当估计一个典型的应用能够使用可复用对象构造的百分比。 此外,论域分析员还应建立一组复用指南,并给出一个例子,说明如何使用论域对象来建立新的应用。 总之,论域分析实际上是一种学习,涉及与应用论域有关的所有知识。论域的边界可能很模糊,很多是凭借经验和实际考虑(如可用资源)。主要思想是想把考虑的论域放宽一些,把相关的概念都标识到,以帮助更好地掌握应用的核心知识。当用户改变他们对系统的需求时,范围广泛的分析可以帮助预测这些变化。 (4) 应用分析 应用分析的依据是在论域分析时建立起来的论域分析模型,并把它用于当前正在建立的应用当中。客户对系统的需求可以当做限制来使用,用它们缩减论域的信息量。就这一点来说,保留的信息受到论域分析视野的影响。论域分析产生的模型并不需要用任何基于计算机系统的程序设计语言来表示,而应用分析阶段产生影响的条件则伴随着某种基于计算机系统的程序设计语言的表示。响应时间需求、用户界面需求和某些特殊的需求,如数据安全等,都在这一层分解抽出。 许多模型识别的要求是针对不止一个应用的。通常我们着重考虑两个方面:应用视图和类视图。必须对每个类的规格说明和操作详细化,还必须对形成应用结构的类之间的相互作用加以表示。 4. 面向对象设计(OOD) OOA和OOD之间有密切的衔接关系,从OOA到OOD是一个逐渐扩充模型的过程。分析处理以问题为中心,可以不考虑任何与特定计算机有关的问题,而OOD则把我们带进了面向计算机的“实地”开发活动中去。通常,OOD分为两个阶段,即高层设计和低层设计。高层设计建立应用的体系结构。低层设计集中于类的详细设计。 (1) 高层设计 高层设计阶段开发软件的体系结构,构造软件的总体模型。在这个阶段,标识在计算机环境中进行问题解决工作所需要的概念,并增加了一批需要的类。这些类包括那些可使应用软件与系统的外部世界交互的类。此阶段的输出是适合应用软件要求的类、类间的关系、应用的子系统视图规格说明。通常,利用面向对象设计得到的系统框架如图6.11所示。 ① 高层设计模型 一个典型的高层设计模型即客户-服务器模型,它构造起应用软件的总体模型,这个模型导出的体系结构既可在过程性系统中使用,又可在面向对象的系统中使用。 客户-服务器模型的想法是让系统的一个部分(服务器子系统)提供一组服务给系统的另一个部分(客户子系统)。请求服务的对象都归于客户子系统,而接受请求提供服务的部分就是服务器。X-window系统在用户界面设计时就使用了客户—服务器方法来构造它的图形交互界面。一个应用的核心“引擎”是服务器子系统的客户,如用户界面、打印服务器、数据库服务器等。  图6.11 OOD设计导出的体系结构  图6.12 MVC框架结构 在Smalltalk中使用的软件体系结构是模型/视图/控制器(MVC,Model/View/Controller),参看图6.12。在这个结构中,模型是软件中的应用论域的各种对象,它们的操作独立于用户界面;视图则管理用户界面的输出;而控制器处理软件的输入。输入事件给出要发送给模型的消息。一旦模型改变了它的状态,就立即通过关联机制通知视图,让视图刷新显示。这个关联机制定义了在模型与各个视图之间的关系,它允许模型的运行独立于与它相关联的视图。类似地,控制器在输入事件发生时将对视图及模型进行控制与调度。 此外,在软件体系结构中还使用了许多其它配置。类的配置表示的多是子系统的体系结构而不是系统的总体结构。它们是一些抽象类的集合,可以定义这些抽象类的新的派生类,并把它们实例化,以实现所要求的特定行为,籍此构造要求的应用软件。对于MVC来说,可以通过开发模型的一个派生类,履行与应用相关联的处理,建立这个应用软件。用户界面通过定义视图和控制器的派生类来建立,这些派生类中许多是可复用的既存类,像按钮和对话框等在多数Smalltalk系统中已经存在。这样就导致了新子系统的建立。   ② 高层设计的规则 ( 最小化各构件间的通信:在子系统的各个高层构件之间的通信量应当达到最小。一个用户界面应当能够自行处理交互、错误改正和硬件控制,而不需打扰主应用。 ( 隐藏复杂性:子系统应当把那些成组的类打包,形成高度的内聚。 ( 逻辑功能分组:虽然输入和输出设备可能相互间不通信,但逻辑上把它们归组到一个处理输入/输出的子系统中。这样比较容易识别并定位问题论域中的事件。 类与通过概念封装的子系统十分类似。事实上,每个子系统都可以被当做一个类来实现,这个类聚集它的构件,提供了一组操作。类和子系统的结构是正交的,一个单个类的实例可能是不止一个子系统的一部分。 高层设计阶段增加了一批必要的类,主要包括了那些可使应用软件与系统的外部世界交互的类。这些交互则包括与其它软件系统(如数据库管理系统、鼠标和键盘)的界面,与使用来进行数据收集或者负责控制的硬件设备的界面等。 高层设计可以表征为标识和定义模块的过程。但这种模块可以是单个的类,还可以是由一些类组合成的子系统。定义过程是职责驱动的。 高层设计和类设计这两个阶段是相对封闭的。在这种情况下,应用软件中的每一个事物都是一个对象,包括应用软件自身在内! 根据这个思想,这两个阶段又是连接的。应用软件的设计是大类的设计,这种类设计考察应用软件所期望的每一个行为,并利用这些行为形成应用类的界面。 (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.1 一个面向对象构件库的层次 特定组的构件 ─ 一个小组为他们自己组内所有成员使用而开发   特定项目的构件 ─ 一个小组为某一个项目而开发   特定问题论域的构件 ─ 购自某一个特定论域的软件销售商   一般构件 ─ 购自专门提供构件的销售商   特定语言原操作 ─ 购自一个编译器的销售商   ② 分解 :最初标识的“类”常常是几个概念的组合。在设计时,可能会发现所标识的操作落在分散的几个概念中,或者会发现,数据属性被分开放到模型中拆散概念形成的几个组内。这样我们必须把一个类分成几个类,希望新标识的类容易实现,或者它们已经存在。 ③ 配置 :在设计类时,可能会要求由既存类的实例提供类的某些特性。通过把相应类的实例声明为新类的属性来配置新类。例如,一种仿真服务器可能要求使用一个计时器来跟踪服务时间。设计者不必开发在这个行为中所需的数据和操作,而是应当找到计时器类,并在服务器类的定义中声明它。 ④ 演变 :要开发的新类可能与一个既存类非常类似,但不完全相同。此时,不适宜采用“选择”操作,但可以从一个既存类演变成一个新类,可以利用继承机制来表示一般化-特殊化的关系。特殊化处理有三种可能的方式。 ( 由既存类建立子类 :现要建立一个新类“起重车”。它的许多属性和服务都在既存类“汽车”中。关系如图6.13所示。新类是既存类的特殊情形。这时直接让“起重车”类作为“汽车”类的子类即可。 ② 建立继承层次由既存类建立新类 :现要增加一个新类“拖拉机”。它的属性与服务有的与“汽车”类相同,有的与“汽车”类不同。关系如图6.14所示。这时,调整继承结构。建立一个新的一般的“车辆”类,把“拖拉机”与“汽车”类的共性放到“车辆”类中,“拖拉机”与“汽车”类都成为“车辆”类的子类。“车辆”是抽象类,相关操作到子类“汽车”类去找。 图6.14 调整继承结构 ③ 建立既存类的父类 :另一种情形是想在既存类的基础上加入新类,使得新类成为既存类的一般类。例如,已经存在“三角形”类,“四边形”类,想加入一个“多边形”类,并使之成为“三角形”和“四边形”类的一般类。继承结构如图6.15所示。从这个“多边形”类又可派生出新的类,如“六边形”类。 图6.15 建立一般类 后两种涉及既存类的修改。在这两种情况下,既存类中定义的操作或数据被移到新类中。如果遵循信息隐蔽和数据抽象的原理,这种移动应不影响已有的使用这些类的应用。类的界面保持一致,虽然某些操作是通过继承而不是通过类的定义伸到这个类的。 (4) 类设计方法 通常,类中的实例具有相同的属性和操作,应当建立一个机制来表示类中实例的数据表示、操作定义和引用过程。这时,类的设计是由数据模型化、功能定义和ADT定义混合而成的。类是某些概念的一个数据模型,类的属性就是模型中的数据域,类的操作就是数据模型允许的操作。要明确规定它们两个谁先确定是不可能的,两个处理是互补的。 类的标识有主动和被动之分。被动类是数据为中心的,它们是根据系统的其它对象发送来的消息而修改其封装数据的;主动类则提供许多系统必须履行的基本操作。与被动类的实例(被动对象)一样,主动类的实例(主动对象)接收消息,但这些对象是负责发送追加消息和控制某些应用部分的。在窗口环境,一个窗口是一个被动对象,它基于发送给窗口的消息来显示某些内容。窗口管理器是一个主动对象,它担负着各种在它控制的窗口上的操作。 在被动类与主动类的设计之间不存在明显的差别。在设计主动类时,需要优先确定数据模型,稍后再确定操作;在设计被动类时,把类提供的服务翻译成操作。在标识了服务之后再设计为支持服务所需要的数据。许多类都是这两个极端的混合。 类中对象的组成包括了私有数据结构、共享界面操作和私有操作。而消息则通过界面,执行控制和过程性命令。因此,要分别讨论它们的实现。 类的设计描述包括两部分: ① 协议描述 :协议描述定义了每个类可以接收的消息,建立一个类的界面。协议描述由一组消息及对每个消息的相应注释组成。 ② 实现描述 :实现描述说明了每个操作的实现细节,这些操作应包含在类的消息中。实现描述由以下信息构成: ( 类名和对一个类引用的规格说明 ( 私有数据结构的规格说明,包括数据项和其类型的指示 ( 每个操作的过程描述 实现描述必须包含充足的信息,以提供在协议描述中所描述的所有消息的适当处理。由一个类所提供服务的用户必须熟悉执行服务的协议,即定义“什么”被描述;而服务的提供者(对象类本身)必须关心:服务如何提供给用户,即实现细节的封装问题。 5. 对象模型技术 Rumbaugh等人提出对象模型技术(OMT),它把分析时收集的信息构造在三类模型中,即对象模型、功能模型和动态模型。图6.16给出了这三个模型的建立次序。从功能模型回到对象模型的箭头表明,这个模型化的过程是一个迭代的过程。每一次迭代都将对这三个模型做进一步的检验、细化和充实。 图6.16 对象模型、功能模型和动态模型的建立次序 (1) 对象模型 对象模型是三个模型中最关键的模型,它的作用是描述系统的静态结构,包括构成系统的类和对象,它们的属性和操作,以及它们之间的关系。事实上,这个模型可以看作扩充的实体-关系模型(E-R)。图6.17给出了在对象模型中用以表示类和对象的图形符号。在OMT中,类与类之间的关系叫做关联。关联代表一组存在于两个或多个对象之间的、具有相同结构和含义的具体连接。关联可以是物理的,也可以是逻辑的。 图6.18给出与关联有关的几个特殊图形符号。 图6.18 关联的表示方法和实例 上图是聚合,代表整体与部分关系,右边的例子说明“(文章中的)段落是由多个句子组成”。句子一端的实心圆 ● 表示“多个”,空心圆 ○ 表示“0个”,不加圆表示“1个”。中图是限定,对关联的含义做某种约束。右边的例子表明,附加的限定词在说明“一个目录包含多个文件”的基础上,更明确地指明“每一个文件都可由目录中的文件名属性来唯一地标识”。下图是角色,由于多数关联具有两个端点,因而涉及到两个角色。右边的例子表明,在公司与个人的关联中,公司的角色是雇主,个人的角色是雇员。 此外,还可用连接属性进一步说明对象之间的连接。再看图中例子,由于一个人在成为公司的雇员时才有工资和职务。因此,工资和职务不是个人属性,而是公司与个人之间的连接属性。OMT的提出者指出,这种情况常常意味着有必要定义一个新类来取而代之。 OMT还定义了一般化与特殊化关系(也称为继承性)。一般化与特殊化关系通常包含一个(如果允许多重继承性,也可以是几个)基类和几个派生类。参看图6.19。基类表示了一个较为一般、普遍的概念,而每个派生类则是它的某个特殊形态。因而派生类除了自然地继承基类所具有的属性和操作外,还具有反映自身特点的属性和操作。 图6.19 一般化与特殊化关系(继承性)的表示方法和示例 (2) 动态模型 要想对一个系统了解得比较清楚,首先应考察它的静态结构,即在某一时刻它的对象和这些对象之间相互关系的结构;然后应考察在任何时刻对对象及其关系的改变。系统的这些涉及时序和改变的状况,用动态模型来描述。动态模型着重于系统的控制逻辑。它包括两个图,一是状态图,一是事件追踪图。 ① 状态图 :状态图是一个状态和事件的网络,侧重于描述每一类对象的动态行为。在状态图中,状态是对某一时刻中属性特征的概括。而状态迁移则表示这一类对象在何时对系统内外发生的哪些事件做出何种响应。图6.20给出了状态图的表示方法。图中的椭圆表示状态,状态之间的箭头表示从一个状态到另一个状态的迁移,附加在箭头上的短语说明触发此状态迁移的事件。在图6.20中,“事件A”是一个单纯的事件,而“事件B[条件]”是一个有条件的事件,在给定条件满足时才起作用。OMT区分两种不同的行为,即操作和活动。操作是一个伴随状态迁移的瞬时发生的行为,与触发事件一起表示在有关的状态迁移之上。活动则是发生在某个状态中的行为,往往需要一定的时间来完成,因此与状态名一起出现在有关的状态之中。状态图中所有这些成份都可以根据具体要求而予以取舍。 6.20 状态图的表示方法 对一个事件的响应依赖于接收它的对象的状态,它可以包括状态的改变、发送另一个事件给原来的发送者或第三个对象。对于一给定类的事件、状态及状态的迁移,可以抽象并表示成一个状态图。动态模型由多个状态图组成,对于每一个具有重要动态行为的类都有一个状态图,从而表明整个系统活动的模式。各个状态图并发地执行,并可以独立地改变状态。对于各种类的状态图可以通过共享事件组合到一个动态模型中。 ② 事件 :一个事件发生在某一时刻。一个事件在逻辑上可能领先于另一个事件,或者两者没有关系。两个没有因果关系的事件叫做并发的,它们之间互相不影响。在模型化一个系统时,一般不考虑并发事件的次序,因为它们可能以任一次序发生。一个分布系统的模型必须包括并发事件和活动。事件都是单独发生的,我们把它们纳入事件类中,并给每个事件一个名字,以指明共同结构和行为。对于所有的事件来说,事件发生的时间是隐蔽的。 事件从一个对象向另一个对象传送信息。有些事件类可能传送简单的信号“要发生某件事”,而有些事件类则可能传送数据值。由事件传送的数据值叫做属性,属性可以在事件类名之后用括号列出。 ③ 事件追踪图 :事件追踪图侧重于说明发生于系统执行过程中的一个特定“场景”(即脚本),是完成系统某个功能的一个事件序列。场景通常起始于一个系统外部的输入事件,结束于一个系统外部的输出事件,它可以包括发生在这个期间的系统所有的内部事件,也可以只包括那些撞到的或由系统中某些对象生成的事件。脚本可以是系统执行的历史记录,还可以是执行一个拟议中系统的设想实验。下面是使用电话的一个脚本。这个脚本只包含影响电话的事件。 [1] 打电话者拿起电话受话器 [10] 铃声在打电话者的电话上传出 [2] 电话忙音开始 [11] 接电话者回答 [3] 打电话者拨数字 (8) [12] 接电话者的电话停止振铃 [4] 电话忙音结束 [13] 铃声在打电话者的电话中消失 [5] 打电话者拨数字 (2) [14] 通电话 [6] 打电话者拨数字 (3) [15] 接电话者挂断电话 [7] 打电话者拨数字 (7) [16] 电话切断 [8] 打电话者拨数字 (3) [17] 打电话者挂断电话  图6.21 打电话的事件追踪图 [9] 接电话者的电话开始振铃 每个事件从一个对象向另一个对象传送信息。在写出这个场景后,下一步就是要标识每个事件的发送者对象和接收者对象。各种有关事件的序列关系以及由此而表现出来的对象之间的交互作用可以通过事件追踪图表达。在这个图上,竖线表示对象,带有箭头的横线表示事件,箭头从发送者对象指向接收者对象。时间自上向下延续,与间隔的空间无关。这只是事件序列的表示,没有精确的时序。图6.21就是一个打电话的事件追踪图。此外,并发事件也可以发送。例如,电话线并发地发送事件给打电话者与接电话者。 概括地讲,状态图叙述一个对象的个体行为,而事件追踪图则给出多个对象所表现出来的集体行为。它们从不同侧面来说明同一系统的行为,因此必然存在着密切的内在关系。例如,一个事件追踪图指出某一对象在接受一个事件之后发出另一事件,同一行为在此对象的状态图中也应当有所表示。另外,由于状态图的复杂程度会随着应用问题的增大而呈指数型迅速增加,这使得分析和验证十分困难。针对这个问题,OMT允许将一个复杂状态分解为一些比较简单的子状态,以前者对系统或系统的某一部分做高度的抽象和概括,以后者来提供在前者中省略掉的有关细节。这样,不仅保证了对系统行为的充分说明,而且有效地限制了状态图本身的复杂性。   (3) 功能模型 功能模型着重于系统内部数据的传送和处理。它是模型化三角架的第三只脚。功能模型定义“做什么”,动态模型定义“何时做”,对象模型定义“对谁做”。 功能模型表明,通过计算,从输入数据能得到什么样的输出数据,不考虑参加计算的数据按什么时序执行。功能模型由多个数据流图组成,它们指明从外部输入,通过操作和内部存储,直到外部输出,这整个的数据流情况。功能模型还包括了对象模型内部数据间的限制。数据流图不指出控制或对象的结构信息,它们包含在动态模型和对象模型中。 图6.22给出了数据流图的表示方法。图中的实线表示数据流,虚线表示控制流,圆框代表处理数据的过程,矩形框表示产生与接收数据的对象,平行线框表示数据存储区。熟悉数据流图的人可以发现,它基本上就是传统的数据流图加上控制流。但OMT指出,虽然控制流有时是有用的,但它重复了动态模型中的有关部分,因而应尽量少用。 图6.22 数据流图的表示方法 功能模型中所有的数据流图往往形成一个层次结构。在这个层次结构中,一个数据流图中的过程可以由下一层的数据流图做进一步的说明。一般来讲,高层的过程相应于作用在聚合对象上的操作,而低层的过程则代表作用于一个简单对象上的操作。 (4) 基于三个模型的分析过程 OMT强调对“现实”的模型化。这里,“现实”是指我们所面对的实际应用问题。即就是说,分析模型是对实际问题的抽象。分析模型中的对象代表实际问题中的对象,分析模型中对象之间的关系反映了实际问题中对象之间的关系。与此同时,OMT还对具体做法给出很多建议。比如,如何识别类及类与类之间的关系,何时考虑属性和操作,怎样利用继承对对象模型做进一步的细化等。此外,OMT还建议通过识别触发系统内部活动的事件,以及系统对这些事件的反应来建立动态模型;通过分析输入和输出数据(这些通常是伴随事件的有关参数)的相关性,以及有关的中间结果来建立功能模型。 6. Coad与Yourdon的OOA方法 我们采用Coad与Yourdon提出的面向对象分析方法来讨论分析的过程。这种方法是在信息模型化技术,面向对象程序设计语言及知识库系统的基础上发展起来的。 (1) 面向对象的分析的考虑 Coad与Yourdon认为面向对象分析(OOA)的主要考虑在于与一个特定应用有关的对象以及对象与对象之间在结构与相互作用上的关系。OOA有两个任务。 ( 形式地说明我们所面对的应用问题,最终成为软件系统基本构成的对象,还有系统所必须遵从的,由应用环境所决定的规则和约束。 ( 明确地规定构成系统的对象如何协同合作,完成指定的功能。这种协同在模型中是以一组消息连接来表示的,它们承担了各个对象之间的通信。 通过OOA建立的系统模型是以概念为中心的,因此称为概念模型。这样的模型由一组相关的类组成。OOA可以采用自顶向下的方法,逐层分解建立系统模型,也可以自底向上地从已经定义的基本类出发,逐步构造新的类。软件规格说明就是基于这样的概念模型形成的,以模型描述为基本部分,在加上接口要求、性能限制等其它方面的要求说明。 构造和评审OOA概念模型的顺序和由五个层次组成。这五个层次不是构成软件系统的层次,而是分析过程中的层次,也可以说是问题的不同侧面。每个层次的工作都为系统的规格说明增加了一个组成部分。当五个层次的工作全部完成时,OOA的任务也就完成了。这五个层次是:类与对象、属性、服务、结构和主题。 图6.23给出了这五个层次,以及每个层次中涉及到的主要概念和相应的图形表示。 图6.23 面向对象分析的概念模型 (2) 标识对象和类 OOA的第一个层次是识别类和对象。类和对象是对与应用有关的概念的抽象。不仅是说明应用问题的重要手段,同时也是构成软件系统的基本元素。这一层的工作是整个分析模型的基础。 ① 如何找出对象和类 对于一个给定的问题论域,找出一个适当的对象集合才能确保可复用性和可扩充性,并能借助面向对象的开发模式,提高软件开发的质量和生产率。 在做问题识别时,系统分析员应按以下五个步骤找出描述应用论域的对象和类: ( 亲自到现场了解在相同应用论域中类似系统的运行情况; ( 注意听取该应用论域的专家对问题解决相关问题的解说,并进行讨论; ( 调查以往是否有相同或类似应用论域的面向对象分析的结果。若有,看是否有可复用对象和类。 ( 详细阅读客户提交的问题陈述,并阅读与该应用论域相关的资料或书籍。同时,将在客户的问题陈述中出现的所有名词开列出来。 ( 建立系统原型,交给客户试用,根据客户的意见和要求,修改系统规格说明。 ② 基于语言的信息分析(Linguistic-based Information Analysis,简称LIA) 对象是与应用论域的有关概念紧密结合在一起的。概念的处理主要是基于自然语言:书面语或口语。将语言规则应用到软件系统分析中的处理方法称为基于语言的信息分析(LIA)。LIA的目的是标识出问题论域的所有概念及这些概念之间的关系。有两种十分有效的LIA技术:短语频率分析和矩阵分析。 ( 短语频率分析 :短语频率分析搜索选定的问题陈述,标识可以表示问题论域概念的术语。PFA的优点在于能广泛地标识问题论域的概念集合,并对它们进行评估,判定它们中哪些与目标软件无关,避免因某些主观原因,而导致误选、遗漏或产生冗余。 ( 矩阵分析 :矩阵的行和列是应用论域的概念,这些概念通常可以产生所标识对象的初始集合。矩阵中的元素表示了相对应的行与列上的概念之间的关联关系。初始矩阵可以从问题陈述中机械地构造出来,分析员逐个元素地评审和讨论这个矩阵,标识出应用论域的关系(事务规则)。 ③ 三视图模型3VM(3-View Modeling) 在做面向对象分析时,还用到 3 种非常有用的传统系统分析工具:数据流图、实体–关系图以及状态–迁移图,它们涉及系统的 3 个不同的、相互独立的方面(处理过程、数据和控制),称为三视图模型3VM(3-View Modeling)。 ( 实体–关系图(简称ERD) 在ERD中的实体很有可能成为对象,实体的属性可表示成最终要由对象进行存储的数据,实体之间的关系有可能将建立“关联对象”。此外,表示关系(连接)的基数,以及因果条件可能成为维持这些关系的服务。所以ERD是OOA的一个有力的工具。ERD的问题是:标识的实体可能与应用论域概念无关,且对于那些不存储数据的对象显得无能为力。 ( 数据流模型 有两种形式的数据流模型:上下文图和分层的数据流图。上下文图确定一个全局的系统边界,描述系统和外部实体的交互,这些外部实体应是候选的对象。对象集合必须阐明外部实体的数据流是如何被接收、处理及生成的。分层的数据流图描述系统的功能是如何分解成为一些基本单元的,这些基本单元最终将成为系统的基本处理规格说明 (Primitive Process Specifications, 简称PPSs),以PPSs描述对象的方法或服务。此外,还可用判定表或场景描述系统的功能。 ( 状态–迁移模型 有两种形式的状态–迁移模型:事件–响应模型和状态–迁移图。任何一个系统中的对象必须是事件的识别者或事件的响应者。如果一个对象既不能识别事件的发生,也不能响应任一事件,它就不是属于这个系统的对象。因此,事件–响应模型必须标识出系统必须识别的所发生的每一个事件,以及系统必须作出的预期响应的事件,还必须标识一系列的识别事件的对象、产生响应的对象。状态–迁移图则可用来标识事件识别对象和对事件产生响应的对象,标识保存状态信息的属性。 ② 选择类的原则 ( 对象应该具有记忆其自身状态的能力。而且对象的属性应当是系统所关心的,或是系统正常运行所必需的。 ( 对象应当具有有意义的服务(操作),可用以修改对象本身的状态(属性值)。而且对象可以利用其服务为系统中的其它对象提供外部服务。如果类不需提供任何服务,则此类或对象可不必包含在分析模型中。 ( 对象应当具有多个有意义的属性。仅有一个属性的对象最好表示为其他对象的属性。 ( 为对象定义的属性应适合于对象的所有实例。如果对象的某一个实例不具备某属性,则意味着应用论域中存在尚未发现的继承关系。应该利用继承关系将原来的对象和特殊的实例区分为两个对象。 ( 为对象定义的有关服务应适合于对象的所有实例。 ( 一个类中应当有一个以上的实例:如果一个类中只要一个实例或对象,那么有可能这个类没有存在的必要。但如果一个类虽然只有一个实例,但它反映的应用论域的某一种概念,那么它必须作为一个类存在。 ( 对象应是软件分析模型的必要成分,与设计和实现方法无关。 ( 有时为了提高执行速度,需要增加一些类或对象或属性,用以保存另一些类或对象产生的暂时结果,以避免重复计算。但这应是设计阶段考虑的问题,不应是分析阶段做的事。 在面向对象的分析活动中,对对象的识别和筛选取决于应用问题及其背景。也取决于分析员的主观思维。 (3) 标识结构 OOA的下一个层次是结构层,用于标识了对象之间的组装和继承关系。在OOA中,标识结构是处理OOA模型复杂性的机制之一。典型的结构有两种:一般化–特殊化结构和整体–部分结构,如图6.24和6.25所示。 整体–部分结构也叫做组装结构,表示聚合关系,即由属于不同类的成员聚合而形成新的类,它用符号△表示有向性,在△的上面是一个整体对象,下面是部分对象。 一般化–特殊化结构也叫做分类结构。其中,特殊化类是一般化类的派生类,一般化类是特殊化类的基类。分类结构具有继承性,一般化类和对象的属性和服务一旦被识别,即可在特殊化类和对象中使用。采用继承来显式地表达属性和服务的公共部分,这可以实现在分类结构中恰如其分地分配属性和服务。将共同的属性放在上层,而将特有的属性放在下层;将共同的服务放在上层,而将特有的服务放在下层。分类结构还可以形成层次或网络,以描述复杂的特殊化类,有效地表示公共部分。 图6.24 一般化–特殊化结构的例子 图6.25 整体–部分结构的例子 继承适合于结构、表格和定义,不适用于值。例如,“鸡”是“鸟”的特殊化,它们之间共享了许多属性和服务。如“翅膀”。“鸡”和“鸟”可以共享“翅膀”的定义,但不能共享“翅膀”的特定值。 从整体的视点来看,一个整体–部分结构可看作一个“has a”结构。例如,Vehicle has a Engine 。其中,Vehicle是整体对象,Engine是局部对象。 整体–部分结构不是类与类之间的对应关联,而是类的实例与实例之间的对应关联。一个整体对象可以有不同种类的部分对象,且可以有多个部分对象。因此,在整体–部分连线的两端应标注一个数量(Amount)或范围(Range),以表明一个整体对象可以拥有的部分对象的数目。通常,这种结构表明以下几种关联: ( 总体–部分(Assembly–Parts)关联,如飞机–发动机之间的关系。 ( 包容–内含(Container–Content)关联,如飞机–飞行员之间的关系。 ( 收集–成员(Collection–Members)关联,如机构–职员之间的关系。 从特殊化的视点来看,一个一般化–特殊化结构可以看作是“is a”或“is a kind of”结构。例如,a Truck Vehicle is a Vehicle或a Truck Vehicle is a kind of Vehicle。 一般化–特殊化结构形成的继承关系可能是单继承,也可能是多继承。它们形成一个层次结构。在单继承的层次结构中,派生类之间可能会出现一些冗余信息;而多继承的层次结构常常会导致较多的派生类,增加了模块复杂性,但能够很好地标识共同性。 (4) 标识属性 下一个层次为属性层,属性层包括对象属性和对象之间的关系(实例连接)。对象所保存的信息称为它的属性,通常把它们封装在对象内部。类的属性所描述的是状态信息,每个实例的属性值表达了该实例的具体状态值。实例连接可以看成是一种事务规则或应用论域限制,表达了一个类的实例与其它类的实例的可能对应关系。 ① 标识属性的策略 分析人员应从问题陈述中搞清:哪些性质在当前问题的背景下完全刻画了被标识的某个对象?通常,属性对应于带定语的名词。如“文件的密码”、“学生的出生年月”等等。属性在问题陈述中不一定有完整的显式的描述,要识别出所关心的潜在属性,需要对应用论域有深刻的理解。 ( 每个对象至少应含有一个属性,使得对象的实例能够被唯一地标识。 ( 必须仔细地定义属性的取值。属性的取值必须能应用于对象类中的每一个实例。其取值不能为“不适用”。 ( 出现在一般化–特殊化关系中的对象所继承的属性必须与一般化–特殊化关系一致。子对象不能继承那些不是为该子对象定义的属性。所继承的属性必须在应用论域中有意义。 ( 所有系统的存储数据需求必须说明为属性。 在识别属性的过程中,为避免找出冗余的或不正确的属性,应注意以下问题: ( 对于应用论域中的某个实体,如果不仅其取值有意义,而且它本身也有必要独立存在,那么,应将该实体作为一个对象,而不宜作为另一个对象的属性。 ( 对象的导出属性应当略去。例如,“年龄”是由属性“出生年月”与系统当前日期导出。因此,“年龄”不应作为人的基本属性。 ( 在分析阶段,如果某属性描述对象的外部不可见状态,应将该属性从分析模型中删去。 如果在标识属性的过程中发生以下情况,应考虑调整对象识别的结果: ( 如果属性只适应于对象的某些实例,而不适应于对象的另外一些实例,则往往意味着存在另一类对象,而且这两类对象之间可能存在着继承关系。 ( 仅有一个属性的对象可以标识为其它对象的属性。 ( 对于对象的某一个属性,如果该对象的某一个特定实例针对该属性有多重属性值,则应当将该对象分为几个对象。 通常,属性放在哪一个类中应是很明显的。较一般的属性应放在一般化–特殊化结构中较高层的类或对象中,较特殊的属性应放在较低层的类或对象中。 数据视图 ERD中实体可能对应于某一对象。这样,实体属性就会简单地成为对象属性 。如果实体(如人)不只对应于一类对象,那么这个实体的属性必须分配到OOA模型的不同类的对象之中。 ② 找出实例连接 实例连接是一种表示应用论域的映象模型,它表明某一对象为完成其职责,需要其它对象的参与。这一步骤要为每一个实例连接定义重复度(1:1, 1:m, m:m)。 当标识完实例连接之后,可能需要对对象进行调整。 ( 查看是否有什么属性描述的是多对多的实例连接,如果有,则可能需要增加一个新的“交互”对象。 ( 查看在同一个类各个实例之间的实例连接。对于每一个连接,看是否有属性描述它。如果有,应当建立新的类或对象。例如,“婚姻”是人与人之间的关系,为描述婚姻发生的地点、时间等,除了“人”这个对象外,还需要建立“婚姻”事件对象。 ( 检查对象之间的实例连接情况。如果两个对象之间有两个或两个以上的实例连接时,可能需要加入一个类或对象来区分这些实例连接的含义。 (5) 标识服务 下一个层次称为服务层。在这一层标识对象所执行的服务以及对象之间传递的消息, 建立对象之间的动态关系,其目的在于定义对象的行为和对象之间的通信(消息连接),说明所标识的各种对象是如何共同协作,使系统运作起来。定义服务的步骤如下: ① 标识在每个对象中必须封装的一组服务; ( 简单的服务:每一个类或对象都应具备的服务,这些服务包括:建立和初始化一个新对象,建立或切断对象之间的关联,存取对象的属性值,释放或删除一个对象。 ( 复杂的服务:分为两种。计算服务:利用对象的属性值计算,以实现某种功能;监控服务:处理对外部系统的输入∕输出,外部设备的控制和数据的存取。 ② 将服务与对象的属性相比较,验证其一致性。如果已经标识了对象的属性,那么每个属性必然关联到某个服务,否则该属性就形同虚设,永远不可能被访问; ③ 画出对象之间的消息通信路径,协调系统的行为。 通常消息有以下几类:发送对象激活接收对象;发送对象传送信息给接收对象;发送对象询问接收对象;发送对象请求接收对象提供服务。这几种类型可根据描述对象之间动作关系的动词和句型来区分。对象之间的通信只能通过消息的发送和接收来完成。消息由发送对象传给接收对象,其中包含有发送者希望完成的服务名和相关的参数。 ( 自底向上的方法:访问每一个对象,给出在对象生存期中从建立到消亡的所有状态。每一状态的改变都关联到对象之间消息的传递。从对象着手,逐渐向上分析。 ( 自顶向下的方法:一个对象必须识别某个系统中发生或出现的事件,产生发送给其它对象的消息,由那些对象作出响应。所以对象应能够询问需要执行什么服务,以便接收、处理、产生每个消息。它是从系统行为着手,然后逐渐分析到对象。 当一个对象将一个消息传送给另一个对象时,另一个对象又可传送一个消息给另一个对象,如此下去就可得到一条执行线索。检查所有的执行线索,确定哪些是关键执行线索(Critical Threads of Execution)。这样有助于我们检查模型的完备性。 应当指出的是,从服务层中的消息连接可以看到一个对象在什么状态下对哪个消息做出怎样的反应。也就是说,每个对象被看成一个自动机。系统中所有这样的自动机构成了描述系统动态行为的基础。然而,这种零散的说明让人很难从中形成一个总体的概念。因此,Coad与Yourdon加上了事件–响应对象交互图EROI (类似于OMT的事件追踪图),用来标识和描述对象之间的相互通信。对于每一个事件,EROI图表明了由哪一个对象来识别事件的发生,产生什么消息;其它哪些对象接收这些消息,并产生什么响应。EROI图的表示方法如图6.26所示。 图6.26 事件–响应对象交互图EROI (6) 标识主题 对于复杂的应用,可能会识别出大量的对象,但分析员同时能处理的对象却不宜过多。因此引入主题机制。 通过建立多个主题,可以处理规模比较大的复杂模型,降低系统的复杂性。每个主题可以看作为一个子模型,甚至是一个子系统。在分析的某一时刻,分析人员只需重点关注某个特定的主题域。 可以依据子论域、子系统,甚至组织或地域区分主题。对于非常庞大与复杂的系统,可以建立多级主题,在主题中可以包含另一主题,形成一个层次结构。但要避免将多级主题变成层次分解。 不同的主题之间可以有重叠部分,用自顶向下(先建立主题层)或自底向上(先建立对象–类层和结构层)方式都可以建立主题。何时将主题加入到分析模型中,应当根据待开发系统的开发计划而定。如果开发计划中规定的类或对象的个数不超过40,可以在完全了解所有的类或对象之后再将主题加入。如果系统开发计划规定的系统规模较大,主题则应尽快加入,以便将问题论域划分成若干问题子论域,进而建立工作单元。为作到这一点,需要由有经验的分析员很快标识出类和结构,粗略确定一些暂时的主题,将它们分发给负责开发的各个小组去继续进行分析。在深入了解问题论域和应用系统的职责之后,还需要对那些暂时的主题进行修正及调整。 7. Coad与Yourdon面向对象设计方法 OOD模型类似于构造蓝图,以最完整的形式全面地定义了如何用特定的实现技术建立起一个目标系统。在OOA模型和OOD模型中使用了共同的表示法。这有助于从分析到设计的转换,并有助于在当前的设计和实现中维护OOA模型。 与OOA模型一样,OOD 模型也有 5 层结构,又被划分成了 4 个组成部分:问题论域、用户界面、任务管理和数据管理。这些组成部分把实现技术隐藏起来,使之与系统的基本问题论域行为分离开来。这种策略能够帮助提高产品的可复用性,有助于产品的升级换代。 在OOA中,我们实际上只涉及到问题论域部分,其它三个部分是在OOD中加进来的。由于问题论域部分包括与我们所面对的应用问题直接有关的所有类和对象。由于识别和定义这些类和对象的工作在OOA中已经开始,这里只是对它们做进一步的细化。 在其它的三个部分中,将识别和定义新的类和对象。这些类和对象形成问题论域部分与用户、与外部系统和专用设备,以及与磁盘文件和数据库管理系统的界面。Coad与Yourdon强调这三部分的作用主要是保证系统基本功能的相对独立,以加强软件的可复用性。假如外部的通信系统更新了,相应的通信协议也应有所变化。在这种情况下,我们只需修改任务管理部分中的某些类和对象,而不必对其它几个部分做任何修改。 (1) 问题论域部分(PDC)的设计 完整的未经改动的OOA模型将成为初始的OOD模型的PDC部分。然后根据实现技术及实现方面的限制,对初始PDC部分的模型中的某些类与对象、结构、属性、操作进行组合与分解。但保留在OOA模型中所捕获到的基本的系统行为。如果使用可复用的类,那么它也要引入到PDC中。另外根据OOD的附加原则,增加必要的类、属性和关系。 ① 复用设计 根据问题解决的需要,把从类库或其它来源得到的既存类增加到问题解决方案中去。既存类可以是用面向对象程序语言编写出来的,也可以是用其它语言编写出来的可用程序。要求标明既存类中不需要的属性和操作,把无用的部分维持到最小限度。并且增加从既存类到应用类之间的一般化―特殊化的关系。进一步地,把应用中因继承既存类而成为多余的属性和操作标出。还要修改应用类的结构和连接,必要时把它们变成可复用的既存类。 ② 把应用论域相关的类关联起来 在设计时,从类库中引进一个根类,做为包容类,把所有与应用论域有关的类关联到一起,建立类的层次。把同一应用论域的一些类集合起来,存于类库中。 ③ 加入一般化类以建立类间协议 有时,某些特殊类要求一组类似的服务。在这种情况下,应加入一个一般化的类,定义为所有这些特殊类共用的一组服务名,这些服务都是虚函数。在特殊类中定义其实现。 ④ 调整继承支持级别 在OOA阶段建立的对象模型中可能包括有多继承关系,但实现时使用的程序设计语言可能只有单继承,甚至没有继承机制,这样就需变更PDC中类的层次结构。图6.27(a) 是多继承模式。 (a) 多继承 (b) 通过实例连接分解多继承 (c) 平铺为单继承 图6.27 多继承改为单继承 ( 针对单继承语言的调整 :利用单继承语言,可使用两种方法把多继承结构转变为单继承结构。一是把特殊类的对象看做是一个一般类对象所扮演的角色,通过实例连接把多继承的层次结构转换为单继承的层次结构,如图6.27(b) 所示;一是把多继承的层次结构平铺,成为单继承的层次结构,如图6.27(c) 所示。在这种情况下,有些属性或操作在同层的特殊类中会重复出现。 ( 针对无继承语言的调整 :当使用无继承的程序设计语言时,必须把具有继承关系的类层次结构平铺开来,成为一组类和对象。一般可利用命名惯例,把这些类或对象关联起来。 ⑤ 修改设计以提高性能 提高执行效率和速度是系统设计的主要指标之一。有时,必须改变问题论域的结构以提高效率。如果类之间经常需要传送大量消息,可合并相关的类,使得通信成为对象内的通信,而不是对象间的通信;或者使用全局数据作用域,打破封装的原则,以减少消息传递引起的速度损失。增加某些属性到原来的类中,或增加低层的类,以保存暂时结果,避免每次都要重复计算造成速度损失。 为提高性能,在对OOA模型进行大规模的改动之前,应考虑下面一些问题: ( 如果没有性能准则,不要去人为地建立。当软件运行在一个CPU速度很快的计算机上,并是单机的人机交互时,大多数的时钟周期都用在了等待用户输入上。 ( 不要认为象C++之类的OOPL就一定效率不高。有一些事实表明,非OOPL的紧凑代码的效率比OOPL的效率高近10倍,但在大多数情况下,OOPL的效率损失约为 10%。而且用非OOPL编程会令程序员非常疲劳,容易出错。 ( 提高一个现存系统的工作效率比重新设计一个高效的系统要容易。一开始应当建立一个原始的简单的设计,实现和调试不会太困难。如果对设计有性能要求,只需加入少量的工作就可以了。 ( 通常系统80%的开销都集中在20%的代码段上。与其为了尽量处处节省系统开销而破坏完善的系统结构,还不如找出系统开销最集中的地方,只对那部分做优化。 ( 预测软件开销集中在什么地方是困难的, 进行优化最有效的方法是在系统运行时使用性能监测工具对系统进行观测。一些像继承、动态绑定、消息传递等处理虽然看起来简单,但需要大量的系统开销。有的代码看起来复杂,但效率不见得低。在代码复杂性与运行的低效之间没有相关性。 ( 提高性能最好的方法是采用最出色的解决方案,而不是拼命地去节省几个微秒、几个字节。这个结论在面向对象技术出现是这样,在面向对象论域仍然是这样。 ⑥ 加入较低层的构件 在做面向对象分析时,分析员往往专注于较高层的类和对象,避免考虑太多较低层的实现细节。但在做面向对象设计时,设计师在找出高层的类和对象时,必须考虑到底需要用到哪些较低层的类和对象。 (2) 用户界面部分的设计 通常在OOA阶段给出了所需的属性和操作,在设计阶段必须根据需求把交互的细节加入到用户界面的设计中,包括有效的人机交互所必需的实际显示和输入。如Windows、Pane、Selector等。用户界面部分设计主要由以下几个方面组成。 ① 用户分类 进行用户分类的目的是明确使用对象,针对不同的使用对象设计不同的用户界面,以适合不同用户的需要。分类的原则有: ( 按技能层次分类:外行/初学者/熟练者/专家。 ( 按组织层次分类:行政人员/管理人员/专业技术人员/其它办事员。 ( 按职能分类:顾客/职员 ② 描述人及其任务的场景 对以上定义的每一类用户,列出对以下问题做出的考虑:什么人、目的、特点、成功的关键因素、熟练程度以及任务场景。 ③ 设计命令层 ( 研究现行的人机交互活动的内容和准则。这些准则可以是非正式的,如“输入时眼睛不易疲劳”,也可以是正式规定的; ( 建立一个初始的命令层:可以有多种形式,如一系列 Menu Screens、或一个Menu Bar、或一系列Icons。 ( 细化命令层:这时,要考虑以下几个问题。 ( 排列命令层次。把使用最频繁的操作放在前面;按照用户工作步骤排列。 ( 通过逐步分解,找到整体-部分模式,帮助在命令层中对操作进行分块。 ( 对菜单宽度与深度进行比较,把深度尽量限制在三层之内。 ( 减少操作步骤:在完成必须任务的前提下,把点取、拖动和键盘操作减到最少。 ④ 设计详细的交互 用户界面设计有若干原则,其中包括: ( 一致性:采用一致的术语、一致的步骤和一致的活动。 ( 操作步骤少:减少敲键和鼠标点取的次数,减少完成某件事所需的下拉菜单的距离。 ( 不要“哑播放”:即每当用户等待系统完成一个活动时,要给出一些反馈信息,说明工作正在进展,以及进展的程度。 ( Undo:在操作出现错误时,恢复或部分恢复原来的状态。 ( 减少人脑的记忆负担:不应在一个窗口使用在另一个窗口中记忆或写下的信息;需要人按特定次序记忆的东西应当组织得容易记忆。 ( 学习的时间和效果:提供联机的帮助信息。 ( 趣味性:在外观和感受上,尽量采取图形界面,符合人类习惯,有一定吸引力。 ⑤ 继续做原型 用户界面原型是用户界面设计的重要工作。人需要对提交的人机交互活动进行体验、实地操作,并精炼成一致的模式。使用快速原型工具或应用构造器,对各种命令方式,如菜单、弹出、填充以及快捷命令,做出一些可供选择的原型,让用户使用,收集用户的反映,通过修改、演示的迭代,使界面越来越有效。 ⑥ 设计HIC(人机交互)类 设计HIC类,首先从组织窗口和构件的用户界面界面的设计开始。窗口需要进一步细化,通常包括类窗口、条件窗口、检查窗口、文档窗口、画图窗口、过滤器窗口、模型控制窗口、运行策略窗口、模板窗口等。 每个类包括窗口的菜单条、下拉菜单、弹出菜单的定义。还要定义用于创建菜单、加亮选择项、引用相应的响应的操作。每个类还负责窗口的实际显示。所有有关物理对话的处理都封装在类的内部。必要时,还要增加在窗口中画图形图符的类、在窗口中选择项目的类、字体控制类、支持剪切和粘贴的类等。与机器有关的操作实现应隐蔽在这些类中。 ⑦ 根据图形用户界面进行设计 图形用户界面区分为字型、坐标系统和事件。图形用户界面的字型是字体、字号、样式和颜色的组合。坐标系统主要因素有原点(基准点)、显示分辨率、显示维数等。事件则是图形用户界面程序的核心,操作将对事件做出响应,事件的工作方式有两种:直接方式和排队方式。所谓直接方式,是指每个窗口中的项目有它自己的事件处理程序,一旦事件发生,则系统自动执行相应的事件处理程序。所谓排队方式,是指当事件发生时系统把它排到队列中,每个事件可用一些子程序信息来激发。 (3) 任务管理部分的设计 所谓任务,是进程的别称,是执行一系列活动的一段程序。当系统中有许多并发行为时,需要依照各个行为的协调和通信关系,划分各种任务,以简化并发行为的设计和编码。而任务管理主要包括任务的选择和调整,它的工作有以下几种。 ① 识别事件驱动任务 一些负责与硬件设备通信的任务是事件驱动的,也就是说,这种任务可由事件来激发,而事件常常是当数据到来时发出一个信号。 ② 识别时钟驱动任务 以固定的时间间隔激发这种事件,以执行某些处理。某些人机界面、子系统、任务、处理机或与其它系统需要周期性的通信。 ③ 识别优先任务和关键任务 根据处理的优先级别来安排各个任务。在系统中,有些操作具有高优先级,因此必须在很强的时间限制内完成;有些操作具有较低的优先级,可进行时间要求较低的处理(如后台处理)。通常需要有一个附加的任务,把各个任务分离开来。 所谓关键任务是对系统的成败起关键作用的处理。必须使用附加的任务来分离这种任务,并对其安全性仔细进行设计、编程和测试。 ④ 识别协调者 当有三个或更多的任务时,应当增加一个附加任务,起协调者的作用。它的行为可以用状态转换矩阵来描述。这种任务仅用于协调任务。 ⑤ 评审各个任务 必须对各个任务进行评审,确保它能满足选择任务的工程标准──事件驱动、时钟驱动、优先级/关键任务或协调者。 ⑥ 定义各个任务 定义任务的工作主要包括:它是什么任务、如何协调工作及如何通信。 ( 它是什么任务:为任务命名,并简要说明这个任务。 ( 如何协调工作:定义各个任务如何协调工作。指出它是事件驱动还是时钟驱动。对于事件驱动的任务,描述激发该任务的事件;对于时钟驱动的任务,指明激发之前所经过的时间间隔,同时指出是一次性的还是重复性的时间间隔。 ( 如何通信:定义各个任务之间如何通信。任务从哪里取值,结果送往何方。 ( 一个模版:任务的定义─任务名、描述、优先级、包含的操作、经由谁通信。 (4) 数据管理部分的设计 数据管理部分提供了在数据管理系统中存储和检索对象的基本结构,包括对永久性数据的访问和管理。它分离了数据管理机构所关心的事项,包括文件、关系型DBMS或面向对象DBMS等。 数据管理方法主要有3种:文件管理、关系数据库管理和面向对象库数据管理。 ( 文件管理:提供基本的文件处理能力。 ( 关系数据库管理系统(RDBMS):关系数据库管理系统建立在关系理论的基础上,它使用若干表格来管理数据。通常根据规范化的要求,可对表格和它们的各栏重新组织,以减少数据冗余,保证修改一致性数据不致出错。规范化的要求用“范式”来定义。 ( 面向对象数据库管理系统(OODBMS):通常,面向对象的数据库管理系统以两种方法实现:一是扩充的RDBMS,二是扩充的面向对象程序设计语言(OOPL)。 扩充的RDBMS主要对RDBMS扩充了抽象数据类型和继承性,再加上一些一般用途的操作来创建和操纵类与对象。扩充的OOPL对面向对象程序设计语言嵌入了在数据库中长期管理存储对象的语法和功能。这样,可以统一管理程序中的数据结构和存储的数据结构,为用户提供了一个统一视图,无需在它们之间做数据转换。 (5) 程序设计语言的影响 详细的面向对象设计是与语言有关的。这样,可以有效地为过程型语言、面向软件包的语言、面向对象的语言所用。 一般地,所有的语言都可以完成面向对象实现,但某些语言能够提供更丰富的语法,能够显式地描绘在面向对象分析和面向对象设计过程中所使用的表示法。 ① 面向对象设计与过程型语言 过程型语言,如C、Pascal、FORTRAN、COBOL,只直接支持过程抽象,但可以增加数据抽象及封装(如利用结构化设计的信息隐蔽模块)。虽然某些公共部分可以作为单独的子程序,但无法明确地表示继承性。也无法明确支持整体与部分、类与成员、对象与属性等关系。也许,面向过程型语言的面向对象设计尽管在技术上不令人满意,但它确实还是能成为一种实用的且可行的方法:从面向对象分析,到面向对象设计,再到具有面向对象特性的过程型语言。 ② 面向对象设计与基于对象的语言 基于对象的语言,也叫做面向软件包的语言,如Ada等,能够直接支持过程抽象、数据抽象、封装和对象与属性关系。虽然某些公共部分可作为单独的子程序,但它仍无法明确地表示继承性,也无法明确地表示类与成员、整体与部分的关系。也许,基于对象语言的面向对象设计比较符合人的习惯,它代表着一种可行的开发方法: 从面向对象分析,到面向对象设计,再到具有面向对象特性的基于对象的语言。 ③ 面向对象设计与面向对象的程序设计语言 面向对象的程序设计语言,包括C++、Smalltalk、Actor、Objective-C、Eiffel、Java等,都直接支持过程抽象、数据抽象、封装、继承、以及对象与属性、类与成员关系。虽然它们不明确地支持整体与部分关系,但可以方便地表示组装对象。因此,从面向对象分析,到面向对象设计,再到面向对象程序设计语言(OOPL)是一种与表示法十分一致的策略。 ④ 面向对象设计与面向对象数据库语言(OO-DBL) 面向对象数据库管理系统(OO-DBMS)及其语言(OO-DBL),是面向对象程序设计语言(OOPL)与数据管理能力的组合。OO-DBMS有四种不同的体系结构: ( 大属性:扩充关系型DBMS,使容纳大属性,如一个文档。 ( 松散耦合:一个OOPL与大量的DBMS组合在一起。 ( 紧密耦合:一个OOPL与某个专用的DBMS集成为一个系统。 ( 扩充关系型:扩充关系型DBMS,使容纳“过程”之类的属性。 紧密耦合体系结构在程序设计和数据操纵中使用了同一种语言,它更能显式地表达面向对象分析和面向对象设计的语义。 8. Booch的方法 就面向对象的方法而言,Booch是最早的倡导者之一,他的工作可回溯到80年代初期。 (1) Booch方法的设计过程 Booch认为软件开发是一个螺旋上升的过程。在这个螺旋上升的每个周期中,有以下几个步骤: ① 发现类和对象 从说明应用问题的词法和概念中识别对象,通过对具体对象的抽象化来发现类。 ② 确定它们的含义 进行行为分析,描画相关的场景,考虑已识别的类和对象在完成系统功能上应承担的责任和所起到的作用,在这个基础上确定每一类的属性和操作。 ③ 找出它们之间的相互关系 找出类与类,对象与对象之间的相互关系。密切相关的一些对象协同作业,以完成部分的系统功能,同时也构成系统的一个必要组成部分,Booch称之为“机构”。识别这样的机构也是这一步的一个重要目的。 ④ 说明每一个类和对象的界面和实现 说明每一类的界面和实现,同时将类和对象分配到不同的模块中,将可同时执行的进程分配到不同的处理机上。这一步是对已有定义的细化和完善过程,往往有助于发现新的类和对象,因而导致下一周期的开发工作。 Booch对每一步的目的,具体做法,最后的产品,以及检验方法都做了详细的讨论。图6.28给出了Booch的面向对象的开发模型。这个模型分为逻辑设计和物理设计两个部分。逻辑设计部分包括两个文件:类图和对象图,着重于类和对象的定义。物理设计部分也包括两个文件:模块图和进程图,针对着软件系统的结构。 Booch还区分静态模型和动态模型。静态模型侧重于系统的构成和结构,而动态模型则侧重于系统在执行过程中的行为。除了上面提到的几个基本文件以外,Booch的方法还包括状态迁移图和时序图,这两个文件主要用于描述系统的动态行为。 (2) Booch方法的基本的模型 在Booch方法中,用于说明系统要求的表示方法和手段非常丰富,相当灵活。 ① 类图 类图用于表示类的存在以及类与类之间的相互关系,是从系统构成的角度来描述正在开发的系统。图6.29所给出的以虚线为边界的云状图符(云图)表示一个类。它的名字,属性和操作则可列于其中。所有的图形表示都伴随有详细的文字说明,不必将全部细节都画在图中。 一个类的存在不是孤立的。类与类之间以不同方式互相合作,共同完成某些系统功能。图6.30给出了几种基本的关系及其图形表示。关联关系表示两个类之间存在着某种语义上的联系,其真正含义要由附加在横线之上的一个短语来予以说明。在表示继承关系的图符中,箭头由子类指向基类。在表示包含关系的图符中,带有实心圆的一端表示整体,相反的一端表示部分。在表示使用关系的图符中,带有空心圆的一端连接在请求服务的类,相反的一端连接在提供服务的类。图6.31给出了一个有关温室管理系统的类图。 ② 对象图 对象图用于表示对象的存在以及它们间的相互关系。在系统的生存期中,类的存在基本上是稳定的,而对象则不断地从产生到消灭,经历着一系列的变化。一个对象图则是描述在这个过程中某一时刻的场景,也可以说是用来说明决定系统行为的基本结构。 从图6.32中的对象图中可以看到,用实线的云状图符表示对象,右下侧有黑影的云状图符表示系统程序库中的公用程序,云状图符之间的连线及其之上的箭头表示消息的传送和方向,为表示消息传送的次序,序列号码附加在每个消息之间。 (3) 状态迁移图 状态迁移图用来说明每一类的状态空间、触发状态迁移的事件,以及在状态迁移所执行的操作。它提供了描述一个类的动态行为的手段。 图6.33给出了“环境控制器”类的状态迁移图。从图中可以看到Booch的表示方法非常类似于OMT的表示方法。同样地,Booch也主张采用类似结构化的方法来减少状态迁移图的复杂性。 (4) 交互作用图 Booch方法中的交互作用图不仅在概念上,而且在表示方法上都与OMT的事件追踪图十分相似。不同的是Booch的交互作用图主要表示操作而不是事件。交互作用图用于追踪系统执行过程中的一个可能的场景,也就是几个对象在共同完成某一系统功能中所表现出来的交互关系。Booch指出交互作用图实际上是另一种形式的对象图,完全有可能利用软件工具在一个图的基础上自动地生成另一个。图6.34的交互作用图与图6.32的对象图表现系统执行过程中的同一场景,但以不同的形式来表达。交互作用图可以清楚地展示消息传送的序列。而对象图则能够表现较为复杂的操作调用。因而,Booch的方法包括这两种图。 (5) 模块图 模块图在系统的物理设计中说明如何将类和对象分配到不同的软件模块中。具体方法与最后代码编写时所采用的程序设计语言有关。在多数语言中,文件就是基本的模块。 图6.35给出了一个模块图的例子。图中的方形图符用以表示声明文件,其右下策的黑影表示相应的定义文件。从一个文件图符到另一个文件图符的箭头表示两者之间的编译依赖关系,也就是说,在箭头所指向的文件编译完成之后,相反一端的文件不可进行编译。 除了文件层的模块图以外,Booch的方法还建议用子系统层的模块图来描述系统的主要部分之间以及与外部系统(如数据库)之间的关系。 (6) 进程图 进程图在系统的物理设计中说明如何将可同时执行的进程分配到不同的处理机上。即使对于运行于单处理机之上的系统,进程图也是有用的,因为它可以表示同时处于活动状态的对象,以便决定进程调度方法。图6.36给出的是温室管理系统的进程图。立方形的图符代表进程,它们之间的连线表示进程之间的通信关系。 总之,Booch的方法内涵丰富,涉及到面向对象的软件系统所有各个方面,是广为使用的几个面向对象的方法之一。Booch不仅建立了开发方法,还提出了对设计人员的技术要求,以及在开发过程的不同阶段(分析、设计、代码编写、测试与集成)中资源与人力的分配。此外,他还讨论了设计阶段的工作何时结束,实现阶段的工作何时结束这样一些较为困难的问题。 三、例题分析 【例1】请说明下面有关范型的叙述的正确答案。 (1) 问题的解决是基于规则的,它把有关问题的知识分解成一组具体规则,用语言的if_then等结构来表示这些规则。 (2) 问题的解决把软件视为由一系列步骤构成的算法。每一步骤都是带有预定输入和特定输出的一个过程,连贯起来产生合理的稳定的贯通于整个程序的控制流。 (3) 把一个问题分解成独立执行的模块。让不只一个程序(进程)同时运行。这些进程互相配合,解决问题。 (4) 把标识和模型化问题论域中的主要实体做为系统开发的起点,主要考虑对象的行为而不是必须执行的一系列动作。 供选择的答案: ① 面向存取 ② 面向对象 ③ 过程性 ④ 逻辑性 ⑤ 函数型 ⑥ 面向进程 ⑦ 说明型 ⑧ 原型 答案:(1) ④ (2) ③ (3) ⑥ (4) ② 分析:逻辑性的范型把有关问题的知识分解成一组具体的规则,这些规则常常是用一种语言的“if_then”结构等来表示。过程性的范型产生过程的抽象,这些抽象把软件视为处理流,定义构成一系列步骤的算法,每一步骤都是带有预定义输入和特定输出的一个过程,把这些步骤串联在一起可产生合理的稳定的贯通于整个程序的控制流。面向对象范型把标识和模型化问题论域中的主要实体做为系统开发的起点,主要考虑对象的行为而不是必须执行的一系列动作。面向对象系统中的对象是数据抽象与过程抽象的一个混合体。表示这些实体的数据抽象是面向对象设计过程的主要产品,系统的状态保存在各个数据抽象的核心所定义的数据存储中。控制流被分成块,并被包括在各个在数据抽象上的各个操作里面。不像在过程性范型里那样,把数据从一个过程传送到另一个过程,而是控制流从一个数据抽象被传送到另一个数据抽象。面向进程的范型把一个问题分解成独立执行的模块。在某种意义上来说,就是让不只一个程序同时运行。这些程序,或更正确地说,这些进程互相配合,从而解决问题。可以认为一个进程在面向进程开发模式中就是一个模块(构件)。面向进程开发模式产生的主要的块是进程。在一个进程中的活动总的来说是独立于其它进程的那些活动的,除非要求从其它进程得到信息,或为其它进程提供信息。甚至可以异步处理,仅需要进程暂停发送或接收信息。在面向对象范型中,各个对象是相对独立的,但也存在单线索控制。面向进程范型支持与面向对象范型相同的封装,但可继续提供多线索(线程)执行。 【例2】对象是面向对象范型的( A )。每个对象可用它自己的一组( B )和它可以执行的一组( C )来表征。应用执行对象的( C )可以改变该对象的( B )。它的应用必须通过( D )的传递。可以认为,这种( D )的传递大致等价于过程性范型中的函数调用。某些语言提供了特殊功能,允许对象引用自己。若一个对象没有显式地被引用,则可让该对象( E )。 供选择的答案: A. ① 基本单位 ② 最小单位 ③ 最大单位 ④ 语法单位 B ( C. ① 行为 ② 功能 ③ 操作 ④ 数据 ⑤ 属性 D. ① 接口 ② 消息 ③ 信息 ④ 操作 ⑤ 过程 E. ① 撤消 ② 歇着 ③ 缺省 ④ 隐式引用 ⑤ 引用自己 答案:A. ①, B. ⑤, C. ③, D. ② E. ③ 分析:对象是面向对象范型的基本单位。每个对象可用它自己的一组属性和它可以执行的一组操作来表征。应用执行对象的操作可以改变该对象的属性,属性一般只能通过操作来改变。它的应用必须通过消息的传递。发送给一个对象的消息定义了一个方法名和一个参数表(可能是空的),并指定某一个对象。而由一个对象接收的消息则引用消息中指定的方法的代码,并将实际参数与参数表中相应的形式参数结合起来。接收对象对消息的处理可能会改变对象中的状态,即改变接收对象的属性,并发送一个消息给自己或另一个对象。可以认为,这种消息的传递大致等价于过程性范型中的函数调用。然而,执行方法得到消息结果的目的是想修改相关对象的内部状态,而不只是修改变元并返回它们。某些语言提供了特殊功能,像smalltalk提供了self,允许对象引用自己。若一个对象没有显式地被引用,则可让该对象缺省。 【例3】在面向对象软件开发过程中特别重视复用。软件构件应独立于当初开发它们的应用而存在。在以后的应用开发中,可以调整这些独立构件以适应新问题的需要。因此,应使得类成为一个( A )的单元。这样就有一个( B )生存期问题。( B )生存期有自己的步骤,与任一特定应用的开发( C )。按照这些步骤,可以完整地描述一个基本( D )。而不仅仅考虑当前正在开发的系统。系统开发的各个阶段都可能会标识新的类。随着各个新类的标识,( B )生存期引导开发工作逐个阶段循序渐进。 在设计与实现类时,应尽可能利用既存类提供为当前应用所需要的功能,利用既存类的三个可能途径是:( E )复用既存类;对既存类进行( F )以得到满足要求的类;重新开始进行开发。 供选择的答案: A. ① 可复用 ② 可测试 ③ 可适用 ④ 可靠 B. ① 应用 ② 寿命 ③ 类 ④ 软件 C. ① 相关 ② 密切相关 ③ 负相关 ④ 无关 D. ① 概念 ② 实体 ③ 事件 ④ 事情 E, F. ① 修改 ② 更新 ③ 照原样 ④ 演化 答案:A. ①, B. ③, C. ④, D. ②, E. ③, F. ④ 分析:在面向对象软件开发过程中特别重视复用。软件构件应独立于当初开发它们的应用而存在。构件的开发瞄准某些局部的设计和实现,它们可用于当前问题的解决,但为了在以后的项目中使用,它们还应当足够通用。在以后的应用开发中,可以调整这些独立构件以适应新问题的需要。为使得类成为一个可复用的单元,有一个类生存期的问题。 类生存期与应用生存期交叉。在应用生存期的每一个阶段都可做类的标识。类生存期有自己的步骤,与任一特定应用的开发无关。按照这些步骤,可以完整地描述一个基本实体。而不仅仅考虑当前正在开发的系统。系统开发的各个阶段都可能会标识新的类。随着各个新类的标识,类生存期引导开发工作逐个阶段循序渐进。 在设计与实现类的时候,有三种利用既存类的途径: ( 原封不动地复用既存类。 ( 对既存类进行演化以得到满足要求的类。 ( 重新开始进行开发。 【例4】类常常被看做是一个抽象数据类型的实现,更合适的是把类看做是某种( A )的一个模型。事实上,类是单个的( B )语义单元。类的用户能够操纵的操作叫做类的( C )。类定义的其余部分给出数据定义和辅助功能定义,包括类的实现。 类的实现常常包括了其它类的实例,这些实例( D )被其它对象存取,包括同一个类的其它实例。类的实现可能还包括某些私有方法,实现它们的类可以使用,而其它任何对象都不能使用。 类,就它是一个数据值的聚合的意义上来看,与Pascal中的记录或C中的结构类似,但又有差别。类扩展了通常的记录语义,可提供各种级别的( E )。类不同于记录,因为它们包括了操作的定义,这些操作与类中声明的数据值有相同的地位。 供选择的答案: A. ① 功能 ② 概念 ③ 结构 ④ 数据 B. ① 语法 ② 词法 ③ 语义 ④ 上下文环境 C. ① 界面 ② 操作 ③ 行为 ④ 活动 D. ① 可自由地 ② 可有控制地 ③ 可通过继承 ④ 应受保护不 E. ① 可移植性 ② 可重复性 ③ 可访问性 ④ 继承性 答案:A. ②, B. ③, C. ①, D. ④, E. ③ 分析:类常常被看做是一个抽象数据类型的实现。这个定义不太够,更合适的是把类看做是某种概念的一个模型。事实上,类是单个的语义单元,它可以更自然地管理系统中的对象,匹配数据定义与操作。许多面向对象范型的语言都提供数据抽象机制。这个机制为类定义提供了一个手段,以指明类的用户能够操纵的操作。这组操作叫做类的界面。类定义的其余部分给出数据定义和辅助功能定义,包括类的实现。这种分离把类的用户与类内部修改的影响隔离开来。类的实现常常包括了其它类的实例,它们往往提供了新类所需要的服务。这些实例应受保护不被其它对象存取,包括同一个类的其它实例。类的实现可能还包括某些私有方法,实现它们的类可以使用,而其它任何对象都不能使用。 类,就它是一个数据值的聚合的意义上来看,与Pascal中的记录或C中的结构类似,但又有差别。类扩展了通常的记录语义,可提供各种级别的可访问性。也就是说,记录的某些成份可能是不可访问的,而这些成份对于本记录型来说具有可访问性。类不同于记录,因为它们包括了操作的定义,这些操作与类中声明的数据值有相同的地位。 【例5】 有一种非形式的技术,对于捕获信息有时很有用,它就是CRC卡片。CRC是( A )、( B )和( C )的缩写。它可以用来组织在每一个子系统中的类。以CRC卡片为辅助工具的设计有以下几个步骤:识别( A )和( B ),分配( B ),找寻( C ), 细化。CRC的作者强调模拟在执行每个基本功能时系统内部出现的( D ),以此推动细化工作的进行。在这个过程中,CRC卡片是十分重要的一个工具。 用CRC卡片来进行设计,既不是传统的“自上而下”,也不是“自下而上”,而是从已知到未知的逐步( E )的过程。 供选择的答案: A ( C. ① 控制 ② 协作者 ③ 可靠性 ④ 类 ⑤ 计算 ⑥ 职责 ⑦ 比较 ⑧ 上下文环境 D. ① 场景 ② 算法 ③ 进程 ④ 变换 E. ① 演化 ② 进展 ③ 展开 ④ 认识 答案:A. ④, B. ⑥, C. ②, D. ①, E. ③ 分析:CRC是Class(类)、Responsibility(职责)和Collaborators(协作者)的缩写。这个技术是由Beck等开发的,在标识实体和定义界面的行为时在小组对话中使用。 由于它的简单有效,以后逐渐成为职责驱动方法的一部分,并结合到其它的面向对象方法中去。CRC卡片的特点是用人格化的方法,将软件系统中的每个部件,即类,看成一个独立的个体,在探索如何与其它个体携手合作完成某一系统功能中逐步完成自身的定位。为了便于分组,查找,以及修改。每张卡片分为三个部分,分别记录一个类的名称,它的主要职责和协作者。以CRC卡片为辅助工具的设计有以下几个步骤: (1) 识别类和职责:CRC卡片的作者建议使用自然语言的分析方法(LIA),从用户需求规格说明书中的名词,物理和概念的实体中发现有用的类。引进适当的基类或子类,以形成继承关系的层次结构。然后,从用户的需求说明书中寻找对有关的信息和行为的描述,以发现职责。职责就是需做的事,必须完成的任务,后要解决的问题,但不涉及如何实现。 (2) 分配职责:将职责分配到类,并记录在相应的卡片上。CRC的作者指出应尽可能确保行为与有关信息不要分开,与同一实体的信息要集中在一起。如有必要,有些职责可以由几个类共同承担。 (3) 找寻协作者:任何一个类在完成自己的职责时往往都需要其它类的协作。这一步就是要找到与每一个类协作的伙伴,并记录在相应的卡片上。具体做法是依次检查每一类所承担的每一项责任,看是否需要其它类的帮助来完成。如果需要,进一步确定需要其做什么。 (4) 细化:CRC的作者强调模拟在执行每个基本功能时系统内部出现的场景,以此推动细化工作的进行。 通常在模拟一个场景的过程中,每当一个类开始“执行”时,它的卡片就被拿出来加以讨论,当“控制”传送到另一个类时,我们的注意力就从前一张卡片转移到另一张上去了。不同的场景,包括例外和出错状况,都应逐一加以模拟。在这个过程中,可以验证已有的定义,不断发现新的类、职责以及伙伴。 用CRC卡片来进行设计,既不是传统的“自上而下”,也不是“自下而上”,而是从已知到未知的逐步展开的过程。场景模拟有助于以试验性的方式来从事设计。由于检索卡片灵活、易于修改,不同的设计方案可以很快地形成,并进行检验。 【例6】论域分析的( A )和对应用分析和高层设计的( B )就构成问题论域的模型。已有许多建立这种模型的技术,一种特别适用的技术就是语义数据模型。 语义数据模型来源于Codd的( C )数据模型和实体—联系模型,并对这类模型进行了扩充和一般化。语义数据模型可以表达问题论域的内涵,还可以表示复杂对象和对象之间的联系。语义数据模型与( C )数据模型本来都是在( D )设计时使用的,但它们的范围已经扩展到系统的开发。作为( D )结构标准的ANSI/SPARC建议提出了三层模型:外部模型、概念模型和( E )模型。这三层可以被映象到面向对象设计的三个层次上去。外部模型与概念模型层相当于高层设计阶段。 供选择的答案: A, B. ① 控制 ② 输出 ③ 输入 ④ 处理 ⑤ 计算 ⑥ 解释 ⑦ 比较 ⑧ 创建 C. ① 变换 ② 关系 ③ 抽象 ④ 事务 D. ① 网络 ② 程序 ③ 算法 ④ 数据库 E. ① 低层 ② 底层 ③ 内部 ④ 存储 答案:A. ③, B. ②, C. ②, D. ④, E. ③ 分析:论域分析的输出和对应用分析和高层设计的输入就构成问题论域的模型。一种特别适用的技术就是语义数据模型。语义数据模型来源于Codd的关系数据模型和实体—联系模型,并对这类模型进行了扩充和一般化。语义数据模型可以表达问题论域的内涵,还可以表示复杂对象和对象之间的联系。语义数据模型与关系数据模型本来都是在数据库设计时使用的,但它们的范围已经扩展到系统的开发。作为数据库结构标准的ANSI/SPARC建议提出了三层模型:外部模型、概念模型和内部模型。这三层可以被映象到面向对象设计的三个层次上去,如表所示。外部模型与概念模型层相当于高层设计阶段。 语义数据模型  主 要 特 征  面向对象设计  外部模型  数据的用户视图  类的定义(规格说明)  概念模型  实体及其之间联系的内涵  类之间的应用联系  内部模型  数据的物理模型  类的实现  (1) 外部模型层。外部模型是来自应用的外部现实世界的视图,它反映了用户对问题的理解而不是实现者对问题的理解。在这一层开发的类应具有对应于用户活动的操作规格说明。 (2) 概念模型层。概念模型层考虑在外部模型层所标识的实体之间的联系。这些联系就是可直接观察到的交互关系。在这一层,通常可以由系统的用户来识别和理解这些联系。 联系的重要属性是实例连接。一个联系的实例连接是指在该联系中一个实体的实例对应于该联系中其它实体的实例的数目。 (3) 内部模型层。这一层考虑实体的物理模型,这是我们生存期中的类设计阶段。物理模型包括两类属性:数据和方法。方法属性对实体的行为模型化,而数据属性对实体的状态模型化。在模型中方法分为两种:一种可作为共有界面来使用,而另一种是私有的。 【例7】从分析到设计的过程流如图所示。一旦已经开发完成一个合理完整的( A )模型后,就要着手( B )的设计。这需要描述( B )的特征,以准确表达待实现的用户需求,以及实现需求所必须的支持环境。一旦定义了各个( B ),就开始( C )设计,这时,可利用CRC卡片,将属性转换为( D ),将关系转换为( E )。 供选择的答案: A ( C. ① 分析 ② 系统设计 ③ 模块设计 ④ 子系统设计 ⑤ 对象设计 ⑥ 数据设计 ⑦ 操作设计 ⑧ 行为设计 D ( E. ① 对象 ② 数据结构 ③ 算法 ④ 消息传递 ⑤ 控制 ⑥ 并发处理 ⑦ 进程 ⑧ 过程 答案:A. ①, B. ④, C. ⑤, D. ②, E. ④ 分析:从分析到设计的过程流如图所示。一旦已经开发完成一个合理完整的分析模型后,就要着手子系统的设计。这需要描述子系统的特征,以准确表达待实现的用户需求,以及实现需求所必须的支持环境。 在定义子系统时,需要在用户需求的整个上下文环境中互相协调:各个用户需求分配给哪个子系统?在OOA中定义的对象驻留在哪个子系统内?哪些子系统并发运行?负责协调和控制它们的系统构件是谁?全局资源在子系统中如何管理?等。 在设计子系统的过程中,需要定义4种重要的设计构件: ( 问题论域:负责实现用户需求的子系统。 ( 人机交互:实现用户界面的子系统(包括可复用的GUI子系统)。 ( 任务管理:负责控制和协调各种事件驱动、时钟驱动、并发执行任务的子系统。 ( 数据管理:负责对象存储和检索的子系统。 每一个构件都可以用一些类、必须的关系和行为来定义。构件之间的关系可以通过建立每个构件的消息模型来建立。 一旦定义了各个子系统,包括上述的各个设计构件,就开始对象(类)设计,这时,可利用CRC卡片,将类转换为设计实现,转换工作如图所示。 四、习题 【6-1】 什么叫面向对象?面向对象方法的特点是什么?为什么要用面向对象方法开发软件? 【6-2】什么是“对象”?识别对象时将潜在对象分成7类,试给出这7类对象的名称,并举例说明。 【6-3】什么是“类”? “类”与传统的数据类型有什么关系?有什么区别? 【6-4】基于复用的面向对象开发过程分为哪几个阶段?每一个阶段需要做哪些事情? 【6-5】按照类生存期,类的开发有哪几种方式?每一种方式需要做哪些事情? 【6-6】面向对象开发方法与面向数据流的结构化开发方法有什么不同?使用面向对象开发方法的优点在什么地方? 【6-7】面向对象的程序设计语言具有数据抽象、信息隐蔽、( A )等特征。作为运算单位的对象应具有下列特性:( B )、( C )、( D )。( E )是面向对象的语言。 供选择的答案: A:① 对象调用 ② 对象变换 ③ 非过程性 ④ 信息继承 ⑤ 并发性 B ( D:① 对象把数据和处理数据的操作结合为一体 ② 在程序运行时对象都处于活动状态 ③ 对象在计算中可向其他对象发送消息 ④ 接受消息的对象必须给消息发送者以回答 ⑤ 对象的内部状态只根据外部送来的消息才操作 E: ① C++,SMALLTALK,objectC ② C,Ada,Modula2 ③ PASCAL,C++,APL ④ Ada,objectC,C 【6-8】基于复用的面向对象的需求分析过程主要分为两个阶段:论域分析和应用分析。试讨论它们各自承担什么任务?如何衔接? 【6-9】 建立分析和设计模型的一种重要方法是UML。试问UML是一种什么样的建模方法?它如何表示一个系统? 【6-10】使用面向对象设计方法进行高层设计,以建立系统的体系结构。这样的结构有哪几种主要的组成部分?每一部分所承担的职责是什么? 【6-11】由RumBaugh等人提出的一种面向对象方法叫做对象模型化技术(OMT),即三视点技术,它要求把分析时收集的信息建立在三个模型中。第一个模型是( A ),它的作用是描述系统的静态结构,包括构成系统的对象和类,它们的属性和操作,以及它们之间的联系。第二个模型是( B ),它描述系统的控制逻辑,主要涉及系统中各个对象和类的时序及变化状况。( B )包括两种图, 即( C )和( D )。( C )描述每一类对象的行为,( D )描述发生于系统执行过程中的某一特定场景。第三个模型是( E ),它着重于描述系统内部数据的传送与处理,它由多个数据流图组成。 供选择的答案: A, B, E:① 数据模型 ② 功能模型 ③ 行为模型 ④ 信息模型 ⑤ 原型 ⑥ 动态模型 ⑦ 对象模型 ⑧ 逻辑模型 ⑨ 控制模型 ⑩ 仿真模型 C, D: ① 对象图 ② 概念模型图 ③ 状态迁移图 ④ 数据流程图 ⑤ 时序图 ⑥ 事件追踪图 ⑦ 控制流程图 ⑧ 逻辑模拟图 ⑨ 仿真图 ⑩ 行为图 【6-12】在类的设计中需要遵循的方针是什么?三个主要的设计准则:抽象、信息隐蔽和模块化如何才能作到? 【6-13】在面向对象软件设计过程中,应按如下要求进行类的设计:只有类的共有界面的成员才能成为使用类的操作,这就是软件设计的( A )原则。当且仅当一个操作对类的实例的用户有用时,它才是类公共界面的一个成员,这是软件设计的( B )原则。由同属一个类的操作负担存取或加工类的数据,这是软件设计的( C )原则。两个类之间的交互应当仅涉及参数表,这是软件设计的( D )原则。每个派生类应该当做基类的特殊化来开发,而基类所具有的公共界面成为派生类的共有界面的一个子集,这是软件设计的( E )原则。 供选择的答案: A:① 过程抽象 ② 功能抽象 ③ 信息隐蔽 ④ 共享性 ⑤ 连通性 B:① 标准调用 ② 最小界面 ③ 高耦合 ④ 高效率 ⑤ 可读性 C:① 数据抽象 ② 低内聚 ③ 高内聚 ④ 低复杂度 ⑤ 低强度 D:① 显式信息传递 ② 高内聚 ③ 低内聚 ④ 相互操作性 ⑤ 连接性 E: ① 动态联编 ② 异质表 ③ 信息隐蔽 ④ 多态性 ⑤ 继承性 【6-14】在类的通过复用的设计中,主要的继承关系有哪几种?试举例说明。 五、习题解答 【6-1】关于“面向对象”,有许多不同的看法。Coad和Yourdon给出了一个定义:“面向对象 = 对象 + 类 + 继承 + 消息通信”。如果一个软件系统是使用这样4个概念设计和实现的,则认为这个软件系统是面向对象的。面向对象方法的特点是: ( 方法的唯一性,即方法是对软件开发过程所有阶段进行综合考虑而得到的。 ( 从生存期的一个阶段到下一个阶段的高度连续性,即生存期后一阶段的成果只是在前一阶段成果的补充和修改。 ( 把面向对象分析(OOA)、面向对象设计(OOD)和面向对象程序设计(OOP)集成到生存期的相应阶段。 使用面向对象方法开发软件的好处是: ( 开发方法的唯一性,开发阶段的高度连续性,表示方式的一致性; ( 问题空间实体的自然表示,减轻了设计者的负担,在设计系统之初不必考虑一个很完整的解决方案。 ( 建立稳定的系统结构,可促进复用性,易于维护,易于修改,可合理利用共同性,减少复杂性。 【6-2】对象的定义: ( 对象是面向对象开发模式的基本成分,是现实世界中个体或事物的抽象表示。 ( 每个对象可由一组属性和它可以执行的一组操作来定义。 可能的潜在对象有7类: ( 外部实体:它们产生或接受为目标系统所使用的信息。如各种物理设备、使用人员、其它相关的子系统。 ( 事物:问题的信息域所涉及的概念实体。如各种报告、显示、文字、信号、规格说明等。 ( 事件:系统运行时发生的并需要系统记忆的事件。如状态转换、物理运动等。 ( 角色:与系统有交互的各种人员所扮演的角色。如经理、工程师、销售人员等。 ( 场所或位置:建立系统整体环境或问题上下文的场所、位置。如基于计算机的系统的安装场所等。 ( 组织机构:与应用有关的组织机构。如((组织,((部门等。 ( 结构:定义由一组成分对象组成的聚合对象,或在极端情况下,定义对象的相关类。如传感器、四轮驱动车、计算机等。 【6-3】把具有相同特征和行为的对象归在一起就形成了类。类成为某些对象的模板,抽象地描述了属于该类的全部对象的属性和操作。属于某个类的对象叫做该类的实例。对象的状态则包含在它的实例变量,即实例的属性中。类定义了各个实例所共有的结构,类的每一个实例都可以使用类中定义的操作。实例的当前状态是由实例所执行的操作定义的。 类,就它是一个数据值的聚合的意义上来看,与Pascal中的记录或C中的结构类似,但又有差别。类扩展了通常的记录语义,可提供各种级别的可访问性。也就是说,记录的某些成份可能是不可访问的,而这些成份对于本记录型来说具有可访问性。类不同于记录,因为它们包括了操作的定义,这些操作与类中声明的数据值有相同的地位。  【6-4】基于复用的面向对象开发过程分为6个阶段,如右图中虚线框所围。 ① 论域分析 :论域分析开发问题论域的模型。论域分析应当在应用分析之前进行,我们在了解问题之前应当对问题敞开思想考虑,考察问题论域内的一个较宽的范围,分析覆盖的范围应比直接要解决的问题更多。 ② 应用分析 :应用(或系统)分析细化在论域分析阶段所开发出来的信息,并且把注意力集中于当前要解决的问题。因为通过论域分析,分析人员具有了较宽的论域知识,因而能开发出更好的抽象。 ③ 高层设计 :在一个纯面向对象环境中,软件体系结构设计与类设计常常是同样的过程,但还是应当把体系结构设计与类的设计分开。在高层设计阶段,设计应用系统的顶层视图。这相当于开发一个代表系统的类,通过建立该类的一个实例并发送一个消息给它来完成系统的“执行”。 ④ 类的开发 :根据高层设计所标识的对各个类的要求和类的规格说明,进行类的开发。因为一个应用系统往往是一个类的继承层次。对这些类的开发是最基本的设计活动。 ⑤ 实例的建立:建立各个对象的实例,实现问题的解决方案。 ⑥ 组装测试:按照类与类之间的关系组装一个完整的应用系统的过程中进行的测试。各个类的封装和类测试的完备性可减少组装测试所需要的时间。 【6-5】按照右图所示的类生存期,类的开发有三种方式。 (1) 既存类的复用 只要有可能就应复用既存类。为了达到此目的,开发人员必须能够找到这样一些类,它们都能选用来提供所需要的行为。有时应用要解决的问题与以前遇到的一些问题密切相关,因此那些问题中定义和实现的类可以复用。然而,多数照原样复用被限制在低层上最基本的类,像基本数据结构。对于较一般的结构,可以在实例化时,使用参数来规定它们的行为。 (2) 从既存类进行演化 多数复用情况是一个类已经存在,它提供的行为类似于要为新类定义的行为。开发人员可以使用既存类做为定义新类的起点。新类将根据既存类渐进式地演变而成。这样,在开发一个新类时,只需要花费较少的工作量就能复用许多既存类,得到所需要的新类。演化可以是横向的,也可以是纵向的。横向的演化导致既存类的一个新的版本,而纵向的演化将从既存类导出新类。我们在这里将主要讨论类的纵向渐进式开发。 ① 渐进式设计。设计既存类的一个特殊化类。通过确定新类中打算要的所有成员,设计者可以确定哪些追加的行为可以加到类中去,哪些既存的行为应当重新实现。 ② 渐进式实现。许多实现可以从既存类直接继承;有时可以仅使用很少的新代码就能利用既存类的实现,而这些新代码必须当做老方法的上文或下文进行编写。此外,在渐进式设计阶段增加的那些行为也必须实现。 ③ 渐进式测试。在测试中最花费时间的就是测试用例的生成。许多新类的测试用例可以从既存类的测试用例组中得到。新类的某些部分因为在测试既存类时已经测试过,因此可以不再需要测试。 (3) 从废弃型进行开发 这个分支仅在不得已的情况下使用。任何一个类,只要它的开发不涉及既存类,就可看做是一个新的继承结构的开始。因此,将建立两种类:一种是抽象类,它概括了将要表达的概念;另一种是具体类,它要实现这个概念。 ① 设计。设计阶段需把分析阶段所产生的界面当做输入,并确定类的其它属性。设计给出类的所有细节。这个阶段的输出是有关类的属性的足够的细节,可支持它们的实现。单个类的设计包括构造数据存储,它是类定义的核心。其内部表示还包括一些私有函数,它们实现了共有操作的某些部分。单个类的低层设计还涉及一些重要联系,如继承和组装关系。 ② 实现。通过变量的声明、操作界面的实现及支持界面操作的函数的实现,可实现一个类的预期行为和状态。在变量中存储的数据通常是其它类的实例,它们提供了为该类的开发所需的服务。 ③ 测试。单个的类为测试提供了自然的单元。如果类的定义提供的界面比较狭窄,那么穷举测试就有可能实现。类的测试在最抽象的层次开始,沿继承联系继续向下进行,新的类可以很容易地完全地被测试,而已经测试过的部分就不需要从新测试了。 (4) 求精和维护 传统的维护活动是针对应用的,而求精过程则是针对类,并把类链接在一起的结构的。因为我们利用抽象进行开发,因此,维护部分在任一时间都能修改这些抽象。随着经验的增长,我们可以够标识抽象的抽象,使得继承结构通过泛化增加新的层次,即在既存的根类之上增加新的层次。 【6-6】结构化开发方法是使用最广泛、历史最长的过程化开发方法。结构化开发方法产生过程的抽象,这些抽象把软件视为处理流,定义构成一系列步骤的算法,每一步骤都是带有预定义输入和特定输出的一个过程,把这些步骤串联在一起可产生合理的稳定的贯通于整个程序的控制流。这将最终导致一个很简单的具有静态结构的体系结构。 在结构化开发方法中,数据结构是应算法步骤的要求而开发的。数据结构贯穿于过程,提供过程需要传送给它的操作的信息。系统的状态是一组全局变量,这组全局变量保持了状态的值,把它们从一个过程传送到另一个过程。 结构化开发方法是一种成熟的应用开发过程。对这种方法已经存在许多支持。然而,在大型系统的开发上和在面向用户系统的构造上存在一些问题。改进大型系统开发的技术主要集中在开发数据抽象。日益增多的考虑是使用抽象数据类型,把过程化系统开发过程包括到数据驱动的方法中。随着大型系统的开发,接踵而来的问题就是要把过程抽象与数据抽象方法组合起来,这种需要导致了面向对象开发方法的诞生。 面向对象开发方法是我们分解问题所使用方法演化的结果。在结构化开发方法中过程抽象是优先的,而面向对象开发方法中优先的是实体,即问题论域的对象。在面向对象开发方法中,把标识和模型化问题论域中的主要实体做为系统开发的起点,主要考虑对象的行为而不是必须执行的一系列动作。 面向对象系统中的对象是数据抽象与过程抽象的一个混合体。表示这些实体的数据抽象是面向对象设计过程的主要产品,系统的状态保存在各个数据抽象的核心所定义的数据存储中。控制流被分成块,并被包括在各个在数据抽象上的各个操作里面。不像在结构化开发方法里那样,把数据从一个过程传送到另一个过程,而是控制流从一个数据抽象被传送到另一个数据抽象。完成的系统体系结构更复杂但也更灵活。在块中分离的控制流允许把复杂的动作视为局部的相互影响。 【6-7】A. ④ B. ① C. ③ D. ④ E. ① 其中,B、C、D的答案可互换 面向对象的程序设计语言应具备面向对象方法所要求的4个成分:类、对象、继承和消息通信。类与对象由数据抽象和信息隐蔽得到,此外语言应具有信息继承的机制。 对象由一组属性和它可以执行的一组操作来定义。面向对象的软件系统通过对象间的消息通信和对象执行消息所要求的服务完成系统预定的功能。所以,对象在计算中可向其他对象发送消息,接受消息的对象必须通过响应消息∕执行服务,给消息发送者以回答。 C++,Smalltalk,object C是面向对象的程序设计语言,Ada、Modula2是基于对象的程序设计语言,因为它缺少继承的机制,而Pascal,C,APL等都不是面向对象或基于对象的程序设计语言。 【6-8】论域分析是软件开发方法中一个基本组成部分,它给出的一组抽象是论域的知识的高层表示,用来做为特定系统需求开发的参考。由于分析常常是在超出当前应用的范围进行的,所以这个技术仅在扩充当前系统或将来建立其它系统时找寻复用信息时才是实用的。 论域分析是对与应用开发问题有关的范围十分广泛的知识的学习。论域的边界是模糊的,它们很多是凭借经验和实际考虑(如可用资源)定义的。主要思想是想把考虑的领域放宽一些,把相关的概念都标识到,以帮助更好地掌握应用的核心知识。当用户改变他们对系统需求的想法时,范围广泛的分析可以帮助预测这些变化,并不再需要进一步的分析。 论域分析的目的是标识基本概念,识别论域的特征,把这些概念集成到论域的模型中。这个模型必须包含概念之间的关系和关于每个单独概念的完全信息。这个信息起着一种胶合作用,把所有相关概念并入论域综合视图中去。 论域分析是一个持续的活动,它可以在软件开发的任一时刻进行标识,它还是一个可在超出特定应用的开发范围时仍然能够正常继续的处理。当论域变化时,必须更新那些抽象和关系。论域分析的结果使得我们可积累更多的有关论域的经验和细化我们的抽象。 使用论域抽象做为系统开发基础的好处是可适应性,客户可能改变需求,而且问题环境也可能改变。基于论域信息的应用更容易适应论域内知识的改变和用户需求的变化。 论域分析是一个长期的投资。如果在初始开发时需求发生变化,它可能有更直接的效益;在很大的项目上可能更快地得到回报。最大的价值是抽象的开发,这些抽象表示了一个问题论域中的基本概念,它们形成的软件库还可支持许多应用的开发。 应用(或系统)分析细化在论域分析阶段所开发出来的信息,并且把注意力集中于要解决的精确的问题。应当把客户对系统的需求当做限制来使用,它们缩减了论域的信息量,而这种信息将是主动设计过程的一部分。就这一点来说,保留的信息受到论域分析视野的影响。论域分析产生的模型并不需要用任何基于计算机系统的程序设计语言来表示,而应用分析阶段产生影响的条件则伴随着某种基于计算机系统的程序设计语言的表示。响应时间需求、用户界面需求和某些特殊的需求,如数据安全等,在这一层都被分解提取。 许多模型识别的要求是针对不止一个应用的。通常我们着重考虑两个方面:应用视图和类视图。必须对每个类的规格说明和操作详细化,还必须对形成应用结构的类之间的相互作用加以表示。 【6-9】 UML叫做统一的建模语言,它把Booch、Rumbaugh和Jacobson等各自独立的OOA和OOD方法中最优秀的特色组合成一个统一的方法。UML允许软件工程师使用由一组语法的语义的实用的规则支配的符号来表示分析模型。 在UML中用5种不同的视图来表示一个系统,这些视图从不同的侧面描述系统。每一个视图由一组图形来定义。这些视图概述如下: ( 用户模型视图 :这个视图从用户(在UML中叫做参与者)角度来表示系统。它用使用实例(use case)来建立模型,并用它来描述来自终端用户方面的可用的场景。 ( 结构模型视图 :从系统内部来看数据和功能性。即对静态结构(类、对象和关系)模型化。 ( 行为模型视图 :这种视图表示了系统动态和行为。它还描述了在用户模型视图和结构模型视图中所描述的各种结构元素之间的交互和协作。 ( 实现模型视图 :将系统的结构和行为表达成为易于转换为实现的方式。 ( 环境模型视图 :表示系统实现环境的结构和行为。 通常,UML分析建模的注意力放在系统的用户模型和结构模型视图,而UML设计建模则定位在行为模型、实现模型和环境模型。 【6-10】一个典型的高层设计是在Smalltalk中使用的MVC软件体系结构,即模型/视图/控制器(Model/View/Controller)。在这个结构中,模型是软件中的应用论域的各种对象,它们的操作独立于用户界面;视图则管理用户界面的输出;而控制器处理软件的输入。输入事件给出要发送给模型的消息。一旦模型改变了它的状态,就立即通过关联机制通知视图,让视图刷新显示。这个关联机制定义了在模型与各个视图之间的关系,它允许模型的运行独立于与它相关联的视图。类似地,控制器在输入事件发生时将对视图及模型进行控制与调度。 另一个典型的高层设计是Coad与Yourdon提出的OOD模型。这个模型也有 5 层结构,又被划分成了 4 个组成部分:问题论域、用户界面、任务管理和数据管理。 问题论域部分的职责是:组合所有的论域中定义的类,为应用设计适当的类层次,为适应语言要求简化继承层次,细化设计以改善性能,开发与数据管理部分的接口,做细化时加入低层对象,评审设计并审查对分析模型所做的增补。 人机交互部分的职责是:定义用户类型,开发任务场景,设计用户命令层次,建立原型以细化与用户交互,设计相关的类和类层次,适当时集成GUI类。 任务管理部分的职责是:标识任务的类型(如事件驱动,时钟驱动),建立优先级,标识协调者,为每个任务设计适当的类。 数据管理部分的职责是:设计数据结构和布局,设计管理数据结构所需的服务,标识可以协助实现数据管理的工具,设计适当的类和类层次。 【6-11】A. ⑦ B. ⑥ C. ③ D. ⑥ E. ② 在OMT中,把分析时收集的信息建立在三个模型中。第一个模型是对象模型,它的作用是描述系统的静态结构,包括构成系统的对象和类,它们的属性和操作,以及它们之间的联系。第二个模型是动态模型,它描述系统的控制逻辑,主要涉及系统中各个对象和类的时序及变化状况。动态模型包括两种图, 即状态迁移图和事件追踪图。状态迁移图描述每一类对象的行为,事件追踪图描述发生于系统执行过程中的某一特定场景。第三个模型是功能模型,它着重于描述系统内部数据的传送与处理,它由多个数据流图组成。 【6-12】在设计类时需要遵循的方针是: ( 信息隐蔽:通过信息隐蔽可保护类的存储表示不被其它类的实例直接存取。 ( 消息限制:该类实例的用户应当只能使用界面提供的操作。 ( 狭窄界面:只有对其它类的实例是必要的操作才放到界面上。 ( 强内聚:模块内部各个部分之间应有较强的关系,它们不能分别标识。 ( 弱耦合:一个单独模块应尽量不依赖于其它模块。 ( 显式信息传递:两个类之间的交互应当仅涉及显式信息传递。 ( 派生类当做派生类型:每个派生类应该当做基类的特殊化来开发,而基类所具有的公共界面成为派生类的共有界面的一个子集。 ( 抽象类:某些语言提供了一个类,用它做为继承结构的开始点,所有用户定义的类都直接或间接以这个类为基类。 为了在类的设计中做到抽象、信息隐蔽和模块化: ( 以类作为系统的基本模块单元,通过一般化―特殊化关系和整体―部分关系,搭建整个系统的类层次结构,实现数据抽象和过程抽象; ( 将数据和相关的操作封装在类内部,建立共有、私有和子类型等存取级别,将数据表示定义成为类的私有成员,实现信息隐蔽。 ( 通过建立类属性(类模板),将某些有可复用要求的类设计成在数据类型上通用的可复用的软件构件,这样有助于实现模块化。 【6-13】A. ③ B. ② C. ③ D. ① E. ⑤ 在面向对象软件设计过程中,应按如下要求进行类的设计:只有类的共有界面的成员才能成为使用类的操作,这就是软件设计的信息隐蔽原则。当且仅当一个操作对类的实例的用户有用时,它才是类公共界面的一个成员,这是软件设计的最小界面原则。由同属一个类的操作负担存取或加工类的数据,这是软件设计的高内聚原则。两个类之间的交互应当仅涉及参数表,这是软件设计的显式信息传递原则。每个派生类应该当做基类的特殊化来开发,而基类所具有的公共界面成为派生类的共有界面的一个子集,这是软件设计的继承性原则。 【6-14】在类的通过复用的设计中,主要的继承关系有两大类: ① 配置:利用既存类来设计类,可能会要求由既存类的实例提供类的某些特性。通过把相应类的实例声明为新类的属性来配置新类。例如,一种仿真服务器可能要求使用一个计时器来跟踪服务时间。设计者不必开发在这个行为中所需的数据和操作,而是应当找到计时器类,并在服务器类的定义中声明它。 但如果使用既存类的内部表示来做为新类的内部表示的一部分,这是一种“针对实现”的继承方式,这种继承方式不好。例如,考虑使用继承来实现一个Circle类。Point类可支持Circle类的一部分实现。为了定义一个圆,我们只需要定义一个点和一个值,做为圆的圆心和半径。把Point当做子类,Circle类不但能得到由x和y提供的圆心,而且还能得到一个操作,让圆能够自由移动。但这样做,我们失去了抽象。 ② 演变 :要开发的新类可能与一个既存类非常类似,但不完全相同。此时可以从一个既存类演变成一个新类,可以利用继承机制来表示一般化―特殊化的关系。特殊化处理有三种可能的方式。 ( 如果新的概念是一个既存类所表示概念的一个特殊情况,特殊化运算可以从该既存类的定义产生新类的初始构造,这是典型的类继承的使用。既存类A的数据结构和操作可以成为新类B的一部分,如图(a)所示。既存类A的公共操作成为新类B的共有界面部分。 ( 如果新类比软件库中那些既存类更一般,则新类B不具有既存类A的全部特性,一般化运算把两个类中共同的特性移到新的更高层的类中,高一层的类是B,我们将要设计它。原来的类A成为新类B的子类。如图(b)所示。 ( 一个既存类A与我们设计的新类B共享概念的某一个部分,则两个概念的共同部分形成新类的基础,且既存类与新类两者成为子类,如图(c)所示。