第六章 面向对象的软件工程一、复习要求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 过程性系统和面向对象系统的基本构造④ 面向进程的范型 :面向进程的范型是把一个问题分解成独立执行的模块让不只一个程序同时运行这些进程互相配合,解决问题面向进程范型产生的主要的块是进程一个进程中的活动独立于其它进程的活动,但可以要求从其它进程得到信息,或为其它进程提供信息。
甚至可以异步处理,仅需要进程暂停发送或接收信息在面向对象范型中,各个对象是相对独立的,但也存在单线索(单线程)控制面向进程范型支持与面向对象范型相同的封装,但可提供多线索(多线程)执行图6.2 一个智能数据分析系统⑤ 混合范型 :在大型系统的开发中,很难说哪种范型对整个问题的解决最好系统开发现在有一种补充步骤,可把大型问题分解成一组子问题对于每个子问题可以采用适当的软件范型例如,设计一个智能数据分析系统时,可把它分解为4个子系统,如图6.2所示系统的数据库界面,可以使用面向对象的方法进行设计;智能数据分析用逻辑范型设计;而分析算法则是过程性的;系统通过一个用户界面来实用化,这个用户界面是用面向存取范型设计出来的 这种设计需要有某种实现语言或一组协同语言的支持许多流行的功能不断增强的语言支持不只一种设计范型对于混合范型,现在已经存在不少技术像C++和并发C这样的语言都是多范型语言,支持过程性范型和面向对象范型并发C还支持面向进程范型系统可以使用单一的语言,利用两种或多种范型写成还可以利用可共享数据格式和连接规约的某些语言,把用这些语言分别编写的块链接到某个单一的应用中去2) 面向对象的概念关于“面向对象”,有许多不同的看法。
Coad和Yourdon给出了一个定义:“面向对象 = 对象 + 类 + 继承 + 消息通信”如果一个软件系统是使用这样4个概念设计和实现的,则认为这个软件系统是面向对象的一个面向对象的程序的每一成份应是对象,计算是通过新的对象的建立和对象之间的消息通信来执行的3) 对象(object)图6.3 对象的定义家具属性属性价格尺寸重量位置颜色桌子价格尺寸重量位置颜色一般意义来讲,对象是现实世界中存在的一个事物可以是物理的,如一个家具或桌子,如图6.3所示,可以是概念上的,如一个开发项目对象是构成现实世界的一个独立的单位,具有自己的静态特征(用数据描述)和动态特征(行为或具有的功能) 例如:人的特征:姓名、性别、年龄等,行为:衣、食、住、行等① 对象、属性、操作、消息定义服务服务购买销售称重移动购买销售称重移动对象可以定义为系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位,由一组属性和一组对属性进行操作的服务组成属性一般只能通过执行对象的操作来改变操作又称为方法或服务,在C++中称为成员函数,它描述了对象执行的功能,若通过消息传递,还可以为其它对象使用而所谓的消息是一个对象与另一个对象的通信单元,是要求某个对象执行类中定义的某个操作的规格说明。
发送给一个对象的消息定义了一个操作名和一个参数表(可能是空的),并指定某一个对象由一个对象接收的消息则调用消息中指定的操作,并将传递过来的实际参数与参数表中相应的形式参数结合起来接收对象对消息的处理可能会改变对象中的状态,即改变接收对象的属性,并发送一个消息给自己或另一个对象可以认为,这种消息的传递大致等价于过程性范型中的函数调用② 对象的分类§ 外部实体:与软件系统交换信息的外部设备、相关子系统、操作员或用户等§ 信息结构:问题信息域中的概念实体,如信号、报表、显示信息等§ 需要记忆的事件:在系统运行过程中可能产生并需要系统记忆的事件,如单击鼠标左键、击打键盘“®”键等§ 角色:与软件系统交互的人员所扮演的角色,如经理、部长、技术支持等§ 组织机构:有关机构,如单位、小组等§ 位置:作为系统环境或问题上下文的场所、位置,如客户地址、收件人(机构)地址等§ 操作规程:如操作菜单、某种数据输入过程等在标识对象时必需注意遵循“信息隐蔽”的原则:必需将对象的属性隐藏在对象的内部,使得从对象的外部看不到对象的信息是如何定义的,只能通过该对象界面上的操作来使用这些信息对象的状态通过给对象赋予具体的属性值而得到。
它只能通过该对象的操作来改变对象有两个视图,分别表现在分析设计和实现方面从分析及设计方面来看,对象表示了一种概念,它们把有关的现实世界的实体模型化从实现方面来看,一个对象表示了在应用程序中出现的实体的实际数据结构之所以有两个视图,是为了把说明与实现分离,对数据结构和相关操作的实现进行封装4) 类(class)和实例(instance)把具有相同特征和行为的对象归在一起就形成了类类成为某些对象的模板,抽象地描述了属于该类的全部对象的属性和操作属于某个类的对象叫做该类的实例对象的状态则包含在它的实例变量,即实例的属性中如图6.4所示从“李杰”、“王辉”和“杨芳”等对象可得到类“学生”,而这些对象就称为该类的实例类定义了各个实例所共有的结构,类的每一个实例都可以使用类中定义的操作实例的当前状态是由实例所执行的操作定义的 李杰王辉杨芳学生 属性属性属性属性李杰男广东软件1980.49#楼129室王辉男湖南计算机控制1979.19#楼320室杨芳女北京系统结构1979.125#楼418室姓名性别籍贯专业出生年月住址服务服务服务看书实验上课运动看书实验上课运动看书实验上课运动看书实验上课运动服务图6.4 对象、类与实例面向对象程序设计语言,如C++和 smalltalk都定义了一个new操作,可建立一个类的新实例。
C++还引入了构造函数,用它在声明一个对象时建立实例此外,程序设计语言给出了不同的方法,来撤消(称为析构)实例,即当某些对象不再使用时把它们删去,把存储释放以备其它对象使用C++给出了一个操作delete,可以释放一个对象所用的空间C++还允许每个类定义自己的析构方法,在撤消一个对象时调用它smalltalk没有提供一个机制来撤消对象,但可以进行无用单元收集类常常可看做是一个抽象数据类型(ADT)的实现但更重要的是把类看做是表示某种概念的一个模型事实上,类是单个的语义单元,它可以很自然地管理系统中的对象,匹配数据定义与操作类加进了操作,给通常的记录赋予了语义,可提供各种级别的可访问性5) 继承 (inheritance)如果某几个类之间具有共性的东西(信息结构和行为),抽取出来放在一个一般类中,而将各个类的特有的东西放在特殊类中分别描述,则可建立起特殊类对一般类的继承如图6.5所示,各个特殊类可以从一般类中继承共性,这样避免了重复汽车运货车救火车起重车大轿车图6.5 特殊类对一般类的继承关系图6.6 多继承建立继承结构的好处:教师§ 易编程、易理解 代码短, 结构清晰;退休者§ 易修改:共同部分只要在一处修改即可;§ 易增加新类:只须描述不同部分。
6) 多继承退休教师如果一个类需要用到多个既存类的特征,可以从多个类中继承,称为多继承例如退休教师是继承退休者和教师这两个类的某些特征或行为而得到的一个新类7) 多态性和动态绑定对象互相通信,即一个对象发消息给另一个对象,执行某些行为或又发消息给另外的对象,从而执行系统的功能发送消息的对象可能不知道另一个对象的类型是什么如在C程序中使用命令ClearInt ( ) 时要严格区分该命令适合一个整数,还是一个整数数组但在C++情形,ClearInt ( ) 对两者都适用,它自己判断对象是哪一个这就是多态性它意味着一个操作在不同类中可以有不同的实现方式如清零操作 ClearInt ( ) 针对消息对象是 int array 还是int,其实现是不同的在一个面向对象的多态性语言中,可能代替一个特定类型的类型的集合就是它的子类集合图6.7 4个类的继承层次三角形多边形四边形例如,图6.7给出了 4 个类的继承层次使用这个继承结构,发送给多边形类的所有消息,它的所有子类都能够响应又例如,想要在屏幕上画一系列多边形,多态性允许一个表的元素可以属于一组指定的类型而不仅仅是一个类型,可以认为这是一个类族。
通过遍历这个表,发送给各个表元素以draw消息,画出所有的多边形矩形动态绑定把函数调用与目标代码块的连接延迟到运行时进行这样,只有发送消息时才与接收消息实例的一个操作绑定它与多态性可以使我们建立的系统更灵活,易于扩充做为动态绑定的例子,考虑在多边形类中的方法contains? (aPoint)这个操作可以在类层次的各层重新实现,以有效利用各个子类的特殊的特征例如,假定一个矩形有某些边与屏幕的边平行,这时,检查一个点是否包含在矩形内,比检查一个点是否在一个一般的四边形内的效率要高一些2. 面向对象软件的开发过程面向对象范型不仅是一些具体的软件开发技术与策略,而且是一整套关于如何看待软件系统与现实世界的关系以及如何进行系统构造的软件方法学用面向对象开发方法构造的软件具有以下特点:§ 面向对象的技术建立的模型与客观世界一致,因而便于理解;§ 适应变化的需要,修改局限在模块中;§ 可复用性1) 应用生存期图6.8给出应用生存期模型在图中各个阶段的顺序是线性的,但实际上开发过程不是线性的还没有办法用图来逼真地反映在面向对象开发过程中各个阶段之间的复杂交互有一部分分析工作在设计之前实行,但有些分析工作与其它部分的设计与实现并行进行。
图6.8 一个基于复用的应用生存期开发可复用的软件构件是软件开发过程的一部分面向对象方法以类作为单元,并分别考虑类的生存期与应用生存期类生存期可包含在图6.8中的类开发阶段中,可与应用生存期集成2) 类生存期图6.9 类生存期在面向对象软件开发过程中特别重视复用软件构件应独立于当初开发它们的应用而存在构件的开发瞄准某些局部的设计和实现,它们可用于当前问题的解决,但为了在以后的项目中使用,它们还应当足够通用在以后的应用开发中,可以调整这些独立构件以适应新问题的需要因此,应使得类成为一个可复用的单元,图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.13所示新类是既存类的特殊情形这时直接让“起重车”类作为“汽车”类的子类即可② 建立继承层次由既存类建立新类 :现要增加一个新类“拖拉机”它的属性与服务有的与“汽车”类相同,有的与“汽车”类不同关系如图6.14所示这时,调整继承结构。
建立一个新的一般的“车辆”类,把“拖拉机”与“汽车”类的共性放到“车辆”类中,“拖拉机”与“汽车”类都成为“车辆”类的子类车辆”是抽象类,相关操作到子类“汽车”类去找车辆 拖拉机汽车拖拉机类图6.14 调整继承结构汽车类③ 建立既存类的父类 :另一种情形是想在既存类的基础上加入新类,使得新类成为既存类的一般类例如,已经存在“三角形”类,“四边形”类,想加入一个“多边形”类,并使之成为“三角形”和“四边形”类的一般类继承结构如图6.15所示从这个“多边形”类又可派生出新的类,如“六边形”类多边形三角形类四边形类多边形类六边形四边形三角形图6.15 建立一般类后两种涉及既存类的修改在这两种情况下,既存类中定义的操作或数据被移到新类中如果遵循信息隐蔽和数据抽象的原理,这种移动应不影响已有的使用这些类的应用类的界面保持一致,虽然某些操作是通过继承而不是通过类的定义伸到这个类的4) 类设计方法通常,类中的实例具有相同的属性和操作,应当建立一个机制来表示类中实例的数据表示、操作定义和引用过程这时,类的设计是由数据模型化、功能定义和ADT定义混合而成的类是某些概念的一个数据模型,类的属性就是模型中的数据域,类的操作就是数据模型允许的操作。
要明确规定它们两个谁先确定是不可能的,两个处理是互补的类的标识有主动和被动之分被动类是数据为中心的,它们是根据系统的其它对象发送来的消息而修改其封装数据的;主动类则提供许多系统必须履行的基本操作与被动类的实例(被动对象)一样,主动类的实例(主动对象)接收消息,但这些对象是负责发送追加消息和控制某些应用部分的在窗口环境,一个窗口是一个被动对象,它基于发送给窗口的消息来显示某些内容窗口管理器是一个主动对象,它担负着各种在它控制的窗口上的操作在被动类与主动类的设计之间不存在明显的差别在设计主动类时,需要优先确定数据模型,稍后再确定操作;在设计被动类时,把类提供的服务翻译成操作在标识了服务之后再设计为支持服务所需要的数据许多类都是这两个极端的混合类中对象的组成包括了私有数据结构、共享界面操作和私有操作而消息则通过界面,执行控制和过程性命令因此,要分别讨论它们的实现类的设计描述包括两部分:① 协议描述 :协议描述定义了每个类可以接收的消息,建立一个类的界面协议描述由一组消息及对每个消息的相应注释组成② 实现描述 :实现描述说明了每个操作的实现细节,这些操作应包含在类的消息中实现描述由以下信息构成:§ 类名和对一个类引用的规格说明§ 私有数据结构的规格说明,包括数据项和其类型的指示§ 每个操作的过程描述实现描述必须包含充足的信息,以提供在协议描述中所描述的所有消息的适当处理。
由一个类所提供服务的用户必须熟悉执行服务的协议,即定义“什么”被描述;而服务的提供者(对象类本身)必须关心:服务如何提供给用户,即实现细节的封装问题5. 对象模型技术对象模型动态模型功能模型Rumbaugh等人提出对象模型技术(OMT),它把分析时收集的信息构造在三类模型中,即对象模型、功能模型和动态模型图6.16给出了这三个模型的建立次序从功能模型回到对象模型的箭头表明,这个模型化的过程是一个迭代的过程每一次迭代都将对这三个模型做进一步的检验、细化和充实图6.16 对象模型、功能模型和动态模型的建立次序图6.17 类与对象的表示方法(1) 对象模型 类 实例 示例类名操作(类名) 属性值画图擦图移动正方形边长位置边框颜色填充颜色属性对象模型是三个模型中最关键的模型,它的作用是描述系统的静态结构,包括构成系统的类和对象,它们的属性和操作,以及它们之间的关系事实上,这个模型可以看作扩充的实体-关系模型(E-R)图6.17给出了在对象模型中用以表示类和对象的图形符号在OMT中,类与类之间的关系叫做关联关联代表一组存在于两个或多个对象之间的、具有相同结构和含义的具体连接。
关联可以是物理的,也可以是逻辑的图6.18给出与关联有关的几个特殊图形符号上图)聚合整体句子段落部分(中图)类A 目录限定词(下图)类A文件名句子类B工作名字个人关联类B雇员雇主职务工资公司名字角色角色 图6.18 关联的表示方法和实例上图是聚合,代表整体与部分关系,右边的例子说明“(文章中的)段落是由多个句子组成”句子一端的实心圆 ● 表示“多个”,空心圆 ○ 表示“0个”,不加圆表示“1个”中图是限定,对关联的含义做某种约束右边的例子表明,附加的限定词在说明“一个目录包含多个文件”的基础上,更明确地指明“每一个文件都可由目录中的文件名属性来唯一地标识”下图是角色,由于多数关联具有两个端点,因而涉及到两个角色右边的例子表明,在公司与个人的关联中,公司的角色是雇主,个人的角色是雇员此外,还可用连接属性进一步说明对象之间的连接再看图中例子,由于一个人在成为公司的雇员时才有工资和职务因此,工资和职务不是个人属性,而是公司与个人之间的连接属性OMT的提出者指出,这种情况常常意味着有必要定义一个新类来取而代之OMT还定义了一般化与特殊化关系(也称为继承性)。
一般化与特殊化关系通常包含一个(如果允许多重继承性,也可以是几个)基类和几个派生类参看图6.19基类表示了一个较为一般、普遍的概念,而每个派生类则是它的某个特殊形态因而派生类除了自然地继承基类所具有的属性和操作外,还具有反映自身特点的属性和操作类图 示例图计算月工资年初至今收入计算月工资小时工资额基类雇 员年工资额临时雇员正式雇员计算月工资子类A子类B图6.19 一般化与特殊化关系(继承性)的表示方法和示例 (2) 动态模型要想对一个系统了解得比较清楚,首先应考察它的静态结构,即在某一时刻它的对象和这些对象之间相互关系的结构;然后应考察在任何时刻对对象及其关系的改变系统的这些涉及时序和改变的状况,用动态模型来描述动态模型着重于系统的控制逻辑它包括两个图,一是状态图,一是事件追踪图① 状态图 :状态图是一个状态和事件的网络,侧重于描述每一类对象的动态行为在状态图中,状态是对某一时刻中属性特征的概括而状态迁移则表示这一类对象在何时对系统内外发生的哪些事件做出何种响应图6.20给出了状态图的表示方法图中的椭圆表示状态,状态之间的箭头表示从一个状态到另一个状态的迁移,附加在箭头上的短语说明触发此状态迁移的事件。
在图6.20中,“事件A”是一个单纯的事件,而“事件B[条件]”是一个有条件的事件,在给定条件满足时才起作用OMT区分两种不同的行为,即操作和活动操作是一个伴随状态迁移的瞬时发生的行为,与触发事件一起表示在有关的状态迁移之上活动则是发生在某个状态中的行为,往往需要一定的时间来完成,因此与状态名一起出现在有关的状态之中状态图中所有这些成份都可以根据具体要求而予以取舍状态A∕操作状态A 状态B 活动事件B [条件]终结状态起始状态 6.20 状态图的表示方法对一个事件的响应依赖于接收它的对象的状态,它可以包括状态的改变、发送另一个事件给原来的发送者或第三个对象对于一给定类的事件、状态及状态的迁移,可以抽象并表示成一个状态图动态模型由多个状态图组成,对于每一个具有重要动态行为的类都有一个状态图,从而表明整个系统活动的模式各个状态图并发地执行,并可以独立地改变状态对于各种类的状态图可以通过共享事件组合到一个动态模型中② 事件 :一个事件发生在某一时刻一个事件在逻辑上可能领先于另一个事件,或者两者没有关系两个没有因果关系的事件叫做并发的,它们之间互相不影响。
在模型化一个系统时,一般不考虑并发事件的次序,因为它们可能以任一次序发生一个分布系统的模型必须包括并发事件和活动事件都是单独发生的,我们把它们纳入事件类中,并给每个事件一个名字,以指明共同结构和行为对于所有的事件来说,事件发生的时间是隐蔽的事件从一个对象向另一个对象传送信息有些事件类可能传送简单的信号“要发生某件事”,而有些事件类则可能传送数据值由事件传送的数据值叫做属性,属性可以在事件类名之后用括号列出③ 事件追踪图 :事件追踪图侧重于说明发生于系统执行过程中的一个特定“场景”(即脚本),是完成系统某个功能的一个事件序列场景通常起始于一个系统外部的输入事件,结束于一个系统外。