10 面向对象技术
面向对象(Object-Oriented,OO)方法是一种非常实用的软件开发方法,它已出现就受到软件技术人员的青睐,现在已经成为计算机科学研究的一个重要领域,并逐渐成为软件开发的一种主要方法。面向对象方法一客观世界中的对象为中心,其分析和设计思想符合人们的思维方式,分析和设计的结果与客观世界的实际比较接近,容易本人们所接受。
在面向对象方法中,分析和设计的界限并不明显,它们采用相同的符号表示,能方便地从分析阶段平滑地过渡到设计阶段。
此外,在现实生活中,用户的需求经常会发生变化,但客观世界的对象以及对象间的关系相对比较稳定,因此用面向对象方法分析和设计的结果也相对比较稳定。
10.1 面向对象的基本概念
Peter Coad 和 Edward Yourdon 提出用下面的等式识别面向对象方法。
面向对象 = 对象(object)+分类(classification)+继承(inheritance)+通过消息的通信(communication with messages)
可以说,采用这 4 个概念开发的软件系统是面向对象的。
1、对象
在面向对象的系统中,对象是基本的运行时的实体,它既包括数据(属性),也包括作用于数据的操作(行为)。所以,一个对象把属性和行为封装为一个整体。
封装是一种信息隐藏技术,它的目的是使对象的使用者和生产者分离,使对象的定义和实现分开。
从程序的设计者来看,对象是一个程序模块;从用户来看,对象为他们提供了所希望的行为。
在对象内的操作通常叫作方法。一个对象通常可由对象名、属性和操作三部分组成。
在现实世界中,每个实体都是对象,如学生、汽车、电视机和空调等都是现实世界中的对象。每个对象都有它的属性和操作,如电视机有颜色、音量、亮度、灰度、频道等属性,可以有切换频道、增大/减低音量等操作。电视机的属性值表示了电视机所处的状态,而这些属性只能通过其提供的操作来改变。电视机的组成部分,如显像管、电路板和开关等都封装在电视机箱中,人们不知道也不关心电视机如何实现这些操作的。
2、消息
对象之间进行通信的一种构造叫作消息。当一个消息发送给某个对象时,包含要求接收对象去执行某些活动的信息。接收到信息的对象经过解释,然后予以响应。这种通信机制叫作消息传递。发送消息的对象不需要知道接收消息的对象如何对请求予以响应。
3、类
一个类定义了一组大体上相似的对象。一个类所包含的方法和数据描述一组对象的共同行为和属性。把一组对象的共同特征加以抽象并存储在一个类中的能力,是面向对象技术最重要的一点。是否建立了丰富的类库,是衡量一个面向对象程序设计语言成熟与否的重要标志。
类是在对象之上的抽象,对象是类的具体化,是类的实例(instance)。在分析和设计时,通常把注意力集中在类上,而不是具体的对象。也不必为每个对象逐个定义,只需对类做出定义,而对类的属性的不同赋值即可得到该类的对象实例。
有些类之间存在一般和特殊关系,即一些类是某个类的特殊情况,某个类是一些类的一般情况。这是一种 is-a 关系,即特殊类是一种一般类。
例如“ 汽车 ”类、“ 轮船 ”类、“ 飞机 ”类都是一种“ 交通工具 ”类。特殊类是一般类的子类,一般类是特殊类的父类。
同样“ 汽车 ”类还可以有更特殊的类,如“ 轿车 ”类、“ 货车 ”类等。在这种关系下形成一种层次的关联。
通常把一个类和这个类所有对象成为“ 类及对象 ”或对象类。
4、继承
继承是父类和子类之间共享数据和方法的机制。这是类之间的一种关系,在定义和实现一个类的时候,可以在一个已经存在的类的基础上来进行,把这个已经存在的类定义的内容作为自己的内容,并加入若干新的内容。
如图:表示了父类 A 和它的子类 B之间的继承关系。
一个父类可以有多个子类,这些子类都是父类的特例,父类描述了这些子类的公共属性和操作。一个子类可以继承它的父类(或祖先类)中的属性和操作,这些属性和操作在子类中不必定义,子类中还可以定义自己的属性和操作。
图中,B只从一个父类 A得到继承,叫作“ 单重继承 ”。如果一个子类有两个或更多个父类,则称为“ 多重继承 ”。
5、多态
在收到消息时,对象要予以响应。不同的对象收到同一消息可以产生完全不同的结果,这一现象叫作多态(polymorphism)。在使用多态的时候,用户可以发送一个通用的消息,而实现的细节则由接收对象自行决定。这样,同一消息就可以调用不同的方法。
多态的实现受到继承的支持,利用类的继承的层次关系,这些低层次上生成的对象能给通用消息以不同的响应。
多态有几种不同的形式,Cardelli和Wegner 把它分为 4 类,如下所示。
其中,参数多态和包含多态称为通用的多态,过载多态和强制多态称为特定的多态。
参数多态(parametric)是应用比较广泛的多态,被称为最纯的多态。
采用参数化模板,通过给出不同的类型参数,使得一个结构有多种类型。(类似模板类吧!)
包含多态(Inclusion Polymorphism)在许多语言中都存在,最常见的例子就是类型化,即一个类型是另一个类型的子类型。
同样的操作可用于一个类型及其子类型。(注意是子类型,不是子类。)包含多态一般需要进行运行时的类型检查。
过载(overloading)多态是同一个名字在不同的上下文中所代表的含义。
同一个名(操作符﹑函数名)在不同的上下文中有不同的类型。程序设计语言中基本类型的大多数操作符都是过载多态的。通俗的讲法,就是c++中的函数重载。在此处中“overload”译为“过载”,其实就是所谓的“重载”,也许“overload”就应翻译为“过载,重载”吧,那“override”就只能是“覆盖”了。
强制多态(coercion)编译程序通过语义操作,把操作对象的类型强行加以变换,以符合函数或操作符的要求。程序设计语言中基本类型的大多数操作符,在发生不同类型的数据进行混合运算时,编译程序一般都会进行强制多态。程序员也可以显示地进行强制多态的操作(Casting)。举个例子,比如,int+double,编译系统一般会把int转换为double,然后执行double+double运算,这个int-》double的转换,就实现了强制多态,即可是隐式的,也可显式转换。
6、动态绑定(dynamic binding)
绑定是一个把过程调用和响应调用所需要执行的代码加以结合的过程。
在一般的程序设计语言中,绑定在编译时进行的,叫作静态绑定。
动态绑定则是在运行时进行的,因此,一个给定的过程调用和代码的结合直接调用发生时才进行。
动态绑定是和类的继承以及多态相联系的。在继承关系中,子类是父类的一个特例,所以父类对象可以出现的地方,子类对象也可以出现。
因此在运行过程中,当一个对象发送消息请求服务时,要根据接收对象的具体情况将请求的操作与实现的方法进行连接,即动态绑定。
10.2 面向对象程序设计
面向对象程序设计(Object-Oriented Programming,OOP)的实质是选用一种面向对象程序设计语言(Object-Oriented Programming Language,OOPL),采用对象、类及其相关概念所进行的程序设计。
定义什么是面向对象、什么不是面向对象的程序设计的工作是困难的,这不依赖于程序设计语言(可用C++语言编写纯C语言程序),而依赖于程序设计风格。当然,好的OOPL必须至少支持:
(1)被封装的对象。
(2)类和实例概念
(3)类间的继承性。
(4)多态。
10.2.1 面向对象的好处
1、面向对象是程序设计新范型
程序设计范型(Programming Paradigm)是人们在程序设计时所采用的基本方式模型。程序设计范型决定了程序设计时采用的思维方式、使用的工具,同时又有一定的应用范畴。
程序设计范型的发展经历了过程程序设计、模块化程序设计、函数程序设计、逻辑程序设计,发展到现在的面向对象程序设计范型。
面向对象程序设计范型是在上述的范型之上发展起来的,它的关键在于加入类和继承性。类和继承性的设置,进一步提高了抽象的程度。
2、面向对象的好处
Ian Granham 在《Object-Oriented Methods Principles & Practice》一书中,将面向对象的好处总结如下。
(1)对象技术解决了产品质量和生产率之间的权衡。
精心设计的面向对象系统能作为那些基本上可以重用构件组装而成的系统的基础,从而创造了更高的生产率;重用以前项目中经过测试的那些已经存在的类会使系统具有更高的质量。
(2)面向对象程序设计,特别是继承机制,是的系统具有很高的灵活性和一扩充性。
(3)面向对象是一个能管理复杂性并增强伸缩性的工具。
(4)根据面向对象的观点,以现实世界对应物为基础,把某一领域分割称为各种对象进行分析与设计,常常比自顶想下进行功能分解的分析及设计更自然合理。
(5)从概念模型化到分析、设计、编码可以实现无缝传递。
(6)通过封装进行的信息隐蔽有助于建立安全的系统。
10.2.2 面向对象程序设计语言
在程序设计语言中引入面向对象概念实际上不是最近几年才有的,其根源可以追溯到 20世纪 60 年代后期,那时 Kristen Nyagaard 和 Ole-Johan Dahl在挪威计算中心开发出了一种叫做 Simula67的语言。这种语言在Simula的基础上,第一次引入了类、协同程序(coroutines)和子类的概念,非常类似于今天的面向对象程序设计语言。
20世纪70年代中期,在Xerox 公司的Palo Alto研究中心(Xerox PARC)中产生了第一个完整、健全的面向对象程序设计语言——Smalltalk,以后又进行了改进和扩充,形成了Smalltalk-80。
这种语言中的每一元素都分别是一个对象,从语言本身的实现、程序设计环境,到所支持的程序设计风格,都是面向对象的。Simula67 和Smalltalk的开发,是目前面向对象程序设计和面向对象程序设计语言研究中心大多数工作的七点。
到今天为止,已经有大约 100 多种面向对象的和基于面向对象的程序设计语言。本节以综述的形式介绍几种目前比较流行的或者独特的OOPLs,其中包括Smalltalk、Eiffel、C++和Java。
1.Smalltalk
Smalltalk 语言起源于 20世纪 60 年代末期,主要设计者 Alan Kay 还在犹他大学念研究生时,就认识到可以用Simula语言中的概念来充实他在图形方面的研究工作。Alan Kay 到 Xerox PARC工作后,将这些早期思想发展为基于图形交互式个人工作站概念,并萌发了设计一种新语言支持这些概念的想法。为Smalltalk的开发做出重要工作的另外两人是Adale Goldberg和Dan Ingans。
Smalltalk 在Xerxo PARC 历经了多次重大修改,最终形成了Smalltalk-80,事实上,Smalltalk并不是一种单纯的程序设计语言,而是反映面向对象程序设计思想的程序设计环境。这个系统在系统本身的设计中强调了对象概念的归一性,引入了类、方法、实例等概念和术语,应用了单重继承和动态绑定,成为了OOPLs发展过程中的一个引人注目的里程碑。
在Smalltalk-80 中,除了对象之外没有其他形式的数据,对于一个对象的唯一操作就是向它发消息。在这种语言中,连类也被看成是对象——类是元素的实例。因此,定义一个类就是项元素发送一条消息,请求元素执行它的 new 来生成一个实例,也就是生成这个类,而消息中的参数,就是关于这个类的说明。
Smalltalk-80 全面支持面向对象的概念,
2、Eiffel(艾菲尔铁塔(法国著名建筑))
Eiffel语言是继Smalltalk-80 之后的另一个“ 纯 ”OOPL。这种语言是由 OOP领域中著名的专家 Bertand Meyer 等人 20世纪 80年代后期在 ISE 公司(Interactive Software Engineering Inc.)开发的,它的主要特点是全面的静态类型化、支持多继承。在众多的OOPLs中,Eiffel是最早(1988年)对多重继承提供支持的,它的一些实现策略(如同名冲突处理、异常处理等)已经对后来的OOPLs的设计和实现产生了影响 。
Eiffel也全面支持面向对象的概念
3、C++
C++语言是一种面向对象的强类型化语言,由AT&T的 Bell实验室于1980年推出,目前已被国外许多主要的计算机和软件生产企业选为替代 C语言或与 C语言并存的基本开发工具。
C++语言是 C 语言的一个向上兼容的扩充,而不是一种新语言。C++是一种支持多范型的程序设计语言,它既支持面向对象的程序设计,也支持面向过程的程序设计。它融合了Simula67 的几种面向对象机制 ——类、继承、虚拟函数,借鉴了ALGOL68中的操作符过载、变量声明位置不受限制等特性,形成了一种“ 比Smalltalk 更接近于机器、比C语言更接近于问题 ”的OOPL。
C++支持基本的面向对象概念:对象、类、方法、消息、子类和继承性。它同时支持静态类型和动态类型。
C++关于面向对象概念的术语
C++完全支持多继承,并且通过适用try/throw/catch模式提供了一个完整的异常处理机制。
C++不提供自动的无用存储单元收集。这必须通过程序员来实现,或者通过编程环境提供合适的代码库来予以支持。
4、Java
Java 语言起源于 Oak语言,Oak语言被设计成能运行在设备(如微波炉、摄影机等)的嵌入芯片上。
Java 编译成伪代码(P代码或者字节代码),这需要一个虚拟机来对其进行解释。Java的虚拟机在几乎每一种平台上都可以运行。这是实质上使得开发是与机器无关的,并且提供了通用的可移植性。
Java 把类的概念和接口(interface)的概念区分开来,并试图通过只允许接口的多继承来克服多继承的危险。Java支持基本的面向对象概念。
Java的异常处理机制与C++的try/throw/catch相类似,但更加严密。在Java中,通过声明轻型线程来处理并发性,这些线程通过副作用和同步协议进行通信。
Java Beans 是组件,即类和其所需资源的集合,它们主要被设计用来提供定制的GUI小配件。
Java 有自己的对象请求代理技术——RMI(远程方法调用),这使得应用能够跨越网络来调用执行在其他Java应用程序内的方法。从第2 版起,Java也包含了一个兼容 CORBA的、语言独立的对象请求代理。
Java也包含了用于管理与关系数据库进行了交互的类 JDBC和用于基本图形的类。
10.2.3 程序设计语言中的OOP机制
作为一种程序设计范型,OOP主要解决程序设计方面的问题,因而与程序设计语言的发展密切相关。特定的OOP概念一般是通过OOPLs中特定的语言机制来体现的。
OOP 现在已经扩展到系统分析和软件设计的范畴,出现了面向对象分析和面向对象设计的概念。
1、类
通常,在介绍 OOP的书籍或文章中总是先引入对象的概念,然后从对对象进行抽象的角度来引入类的概念。但是,当设计和实现一个面向对象的程序时,首先接触到的不是对象,而是类和类层次结构。
类的定义:
类具有实例化功能,包括实例生成(由类的Constructor完成)和实例消除(由类的Destructor完成)。类的实例化功能决定了类及其实例具有下面的特征:
(1)同一个类的不同实例具有相同的数据结构,承受的是同一方法集合所定义的操作,因而具有规律相同的行为。
(2)同一个类的不同实例可以持有不同的值,因而可以具有不同的状态。
(3)实例的初始状态(初值)可以在实例化时确定。
2、继承和类层次结构
孤立的类只能描述实体集合的特征同一性,而客观世界中实体集合的划分通常还要考虑实体特征方面有关联的相似性。在OOP中使用继承机制解决这一问题。
在OOPLs中,继承一般通过定义类之间的关系来体现。
在一个面向对象系统中子了与父类之间的继承关系构成了这个系统的层次结构,可以用树(对应于单继承)或格(对应于多继承)这样的图来描述。
当执行一个子类的实例生成方法时,首先在类层次结构中从该子类沿继承路径上溯至它的一个基类,然后自顶向下执行该子类实例生成方法函数体。当执行一个子类的实例消除方法时,顺序恰好与执行该子类实例生成方法相反:先执行该子类实例消除方法的函数体,再沿继承路径自底向上地执行该子类所有父类的实例消除方法。
与一般数据类型的实例化过程相比,类的实例化过程是一种实例的合成过程,而不仅仅是根据单个类型进行的空间分配、初始化和联编。
OOPs中的继承机制体现了一条重要的面向对象程序设计原则:开发人员在构造程序时不必从零开始,而只需对差别进行程序设计。支持继承也是OOPs与传统程序设计语言在语言机制方面最根本的区别。
3、对象、消息传递和方法
对象是类的实例尽管对象的表示在形式上与一般数据类型十分相似,但是它们之间存在一种本质区别:对象之间通过消息传递方式进行通信。
消息传递源是一种与通信有关的概念,OOP使得对象具有交互能力的主要模型就是消息传递模型。对象被看成用传递消息的方式互相联系的通信实体,它们既可以接收,也可以拒绝外界发来的消息。一般情况下,对象接收它能够识别的消息,拒绝它不能识别的消息。对于一个对象而言,任何外部的代码都不能以任何不可预知或事先不允许的方式与这个对象进行交互。
发送一条消息至少给出一个对象的名字和要发给这个对象的那条消息的名字。通常,消息的名字就是这个对象中外界克制的某个方法的名字。在消息中,经常还有一组参数(也就是那个方法所要求的参数),将外界的有关消息传给这个对象。
对于一个类来说,它关于方法界面的定义规定了实例的消息传递协议,而它本身则决定了消息传递的合法范围。由于类是先于对象构造而成的,所以一个类为它实例提供了可预知的交互方式。
4、对象自身引用
对象自身引用(self-reference)是OOPLs中的一种特有结构。这种结构在不同的OOPLs中有不同的名称,在C++和Java中称为 this,在Smalltalk-80、Object-C和其他一些OOPLs中则称为 self。
以 C++语言为例,对于类 c 和方法 C::m,在C::m方法体重出现的c 的成员名 n 将被编译程序按 this->n 来对待。这里的 this 是一个类型为 c* 的指针(在Java语言中 this是一个引用),它的值由语言中的消息传递机制提供。
对象自身引用的值和类型分别扮演了两种意义的角色:对象自身引用的值使得方法体重引用的成员名与特定的对象相关,对象自身引用的类型则决定了方法体被实际共享的范围。
对象自身引用机制使得在进行方法的设计和实现时并不需要考虑与对象联系的细节,而是从更高一级的抽象层次,也就是类的角度来设计同类型对象的行为特征,从而使得方法在一个类及其子类的范围内具有共性。在程序运行过程中,消息传递机制和对象自身引用将方法与特定的对象动态地联系在一起,使得不同的对象在执行同样的方法体时,可以因对象的状态不同而产生不同的行为,从而使得方法对具体的对象有个性。
5、重置
重置(overriding)的基本思想是:通过一种动态绑定机制的支持,使得子类在继承父类界面定义的前提下,用适合于自己要求的实现去置换父类中的相应实现。
在OOPLs中重置机制有相应的语法供开发人员使用。在C++语言中,通过虚拟函数(virtual function)的定义来进行重置的生命,通过虚拟函数跳转表(virtual functions jump tables,vtbl)结构来实现重置方法体的动态绑定。在Java 语言中,通过抽象方法(abstract method)来进行重置的声明,通过方法查找(method lookup)实现重置方法体的动态绑定。
6、类属类
类属是程序设计语言中普遍注重的一种参数多态机制,在OOPLs中也不例外。本节主要介绍与类有关的类属。
在C++ 语言中,类属有专门的术语 ——template
其中,
类属类可以看成是类的模板。一个类属类是关于一组类的一个特性抽象,它强调的是这些类的成员特征中与具体类型无关的那些部分,而与具体类型相关的那些部分则用变元来表示。这就使得对类的集合也可以按照特性的相似性进行划分。类属类的一个重要作用,就是对类库的建设提供了强有力的而支持。
重置和类属类都是一种多态机制。
7、无实例的类
在前几节中曾指出,类是对象的模板,对象是类的实例。那么是否每个类都至少有一个实例?如果在类之间没有定义继承关系,回答是肯定的。这是因为若存在没有实例的类,那么这样的类对程序的行为没有任何贡献,因而是冗余的。相反,如果存在继承关系,那么的确有可能在类层次结构的较高层次上看到始终没有实力的类。
要创创建无实例的类,仍然需要语言支持。在C++和Java语言中,抽象类就是这样的类。在C++中通过类中定义虚函数来创建一个抽象类,在Java 中通过在类中定义抽象方法来创建一个抽象类,或者直接将一个类声明为抽象类。
10.2.4 面向对象的程序
1、类库
类库是一种预先定义的程序库,可以由开发人员自己扩充。
类库以程序模块的形式,按照类层次结构把一组类的定义和实现组织在一起。通常,这一组类预先提供的是一些低层次功能,如输入输出例程、图形操作原语和基本数据结构等,这些功能覆盖了传统例程库所提供的功能。
开发人员可以直接使用类库中的类,其方式与使用语言中基本类型完全相同。开发人员可以扩充类库,办法是定义和实现类库中已有类的子类,再将这样的子类加入类库。
大多数程序设计语言都有类库。最早引入类库的语言Smalltalk,Smalltalk 类库中基本的类层次结构片段
10.3 面向对象开发技术
众所周知,采用系统化的开发软件(特别是大规模软件)系统才获得好的系统。
什么事“ 好 ”系统呢? 这个问题应从使用者(外部)和开发者(内部)两个角度来回答。从使用者观点出发,需要系统具有易学易用、界面友好、正确使用时能快速给出正确结果、效率高等优点,还要求系统安全可靠等;从系统开发者和管理者角度出发,要求系统易于修改和扩充、易于理解、易于测试和重用、易于与其他系统兼容和管理等。虽然不同的系统所强调的特性可能不同,但上述所要求的系统特性是基本特性。
相对而言,面向对象的开发技术更有利于开发具有上述特性的软件系统。
面向对象技术的基本概念和相关步骤:
10.3.1 面向对象分析
同其他分析方法一样,面向对象分析(Object-Oriented Analysis OOA)的目的是为了获得对应用问题的理解。理解的目的是确定系统的功能、性能要求。面向对象分析法与功能 / 数据分析法之间的差别是前期的标识含义不同。
功能 / 数据分析法分开考虑系统的功能要求和数据及其结构,面向对象分析方法是将数据和功能结合在一起作为一个综合对象来考虑。面向对象分析技术可以将系统的行为和信息间的关系表示为迭代构造特性。
面向对象分析包含 5 个活动:认定对象、组织对象、描述对象间的相互作用、定义对象的操作、定义对象的内部信息。
1、认定对象
在应用领域中,按自然存在的实体确立对象。在定义域中,首先将自然村在的“ 名词 ”作为一个对象,这通常是研究问题、定义域实体的良好开始。通过实体间的关系寻找对象常常没有问题,而困难在于寻找(选择)系统关心的实质性对象,实质性对象是系统稳定性的基础。
例如在银行应用系统中,实质性对象应包含客户账务、清算等,而门卫值班表不是实质性对象,甚至可不包含在系统中。
2、组织对象
分析对象间的关系,将相关对象抽象成类,其目的是为了简化关联对象,利用类的继承性建立具有继承性层次的类结构。抽象类时可从对象间的操作或一个对象是另一个对象的一部分来考虑,如房子由们和窗构成,门和窗是房子类的子类。由对象抽象类,通过相关类的集成构造类层次,所以说系统的行为和信息见得分析过程是一种迭代表征过程。
3、对象间的相互作用
描述出各对象在应用系统中的关系,如一个对象是另一个对象的一部分,一个对象与其他对象间的通信关系等。这样可以完整地描述每个对象的环境,由一个对象解释另一个对象,以及一个对象如何生成另一个对象,最后得到对象的界面描述。
4、基于对象的操作
当考虑对象界面时,自然要考虑对象的操作。其操作有从对象直接标识的简单操作,如创建、增加和删除等;也有更复杂的操作,如将几个对象的信息连接起来。
一般而言,避免对象太复杂比较好,当连接的对象太复杂时,可将其标识为新对象。
当确定了对象的操作后,在定义对象的内部,对象内部定义包括其内部数据信息、信息存储方法、继承关系以及可能生成的实例数等属性。
分析阶段最重要的是理解问题域的概念,其结果将影响整个工作。经验表明,从应用定义域概念标识对象是非常合理的,完成上述工作后写出规范文档,文档确定每个对象的范围。
早期面向对象的目标之一是简化模型与问题域之间的语义差距。事实上,面向对象分析的基础是软件系统结构,这依赖于人类看待现实世界的方法。当人们理解求解问题的环境时,常采用对象、分类法和层次性这类术语。面向对象分析与功能 / 数据分析方法相比,面向对象的结果比较容易理解和管理。面向对象分析方法的另一个优点是便于修改,早期阶段的修改容易提高软件的可靠性。
10.3.2 面向对象设计
面向对象设计(Objec-Oriented Design ,OOD)的含义是设计分析模型和实现响应源代码,在目标代码环境中这种源代码可被执行。通常情况是,由概念模型生成的分析模型被装入到相应的执行环境中时,还需要修改。
同所有其他设计活动一样,要使系统的结构好且效率高,做好相互的平衡是困难的。分析模型已经提供了概念结构,它将试图长期保存。另外,设计期必须充分考虑系统的稳定性,如目标环境所需的最大存储空间、可靠性和响应时间等,所有这些都会影响系统的结构。
对象标识期间的目标是分析对象,设计过程也是发现对象的过程,称之为再处理。因此必须有从分析模型到设计模型到程序设计语言的线性转换规则。
对昂可以用预先开发的源代码实现,称这样的部件为构件(component)。由于这些构件是功能和数据的组装,因此,常常用于简化面向对象环境的产生。
如前所述,面向对象系统的开发需要面向对象的程序设计风格,这与OOPL无直接关系。
面向对象是一种程序设计风格,而不只是一种具有构造继承性、封装性和多态的程序设计语言族的命名。
10.3.3 面向对象测试
就测试而言,用面向对象方法开发的系统测试与其他方法开发的测试没什么不同,在所有开发系统红都是根据规范说明来验证系统设计的正确性。程序验证应尽可能早地开始。程序调试步骤是从最底层开始,从单元测试、综合测试到系统测试。
单元测试是系统构建的分体测试。将测试好的系统构建接起来看它们之间相互作用的正确性称为综合测试。最后是整个系统的测试。包括软件系统所在相关环境的测试。通常综合测试是一种“ 主攻 ”活动,在系统开发期是非常关键的。这一阶段应随时连接已开发的每一部分,再看它们的实际工作,这种“ 主攻 ”活动在面向对象系统中是一种实质性的、渐渐增长的测试策略。
面向对象的系统由一些对象和它们相互间的通信组成,这些对象包括了它们的数据属性和操作(动作),比传统系统开发方法中独立工作的子程序大。对象的操作局限于特定数据,一个对象的所有操作自然由同一个设计者开发。这导致单元测试比传统系统的单元大,综合测试尽可能在早期阶段处理,因为通信是系统开发的实质。所有对象有预定义的界面,这也有利于综合测试。当综合测试继续到较高层次时,那么越来越多的对象就会被逐步连接起来。面向对象的综合测试是由底层向上的测试。
一般来说,对面向对象软件的测试可分为下列 4 个层次进行。
(1)算法层。
测试类中定义的每个方法,基本上相当于传统软件测试中的单元测试。
(2)类层
测试封装在同一个类中的所有方法与属性之间的相互作用。在面向对象软件中,类是基本模块,因此可以认为这是面向对象测试中所特有的模块测试。
(3)模块层
测试一组协同工作的类之间的相互作用。大体上相当于传统软件测试中的集成测试,但也有面向对象软件的特点(例如,对象之间通过发送消息相互作用)。
(4)系统层
把各个子系统组装成完整的面向对象软件系统,在组装过程中同时进行测试。
软件工程中传统的测试用例设计技术,如逻辑覆盖,等价类划分和边界值分析等方法,仍然可以作为测试类中每个方法的主要技术。面向对象测试的主要目标也是用尽可能低的测试成本和尽可能少的测试用例,发现尽可能多的错误。
但是,面向对象程序中特有的封装、继承和多态机制,也给面向对象测试带来一些新特点,增加了测试和调试的难度。
10.4 面向对象分析与设计方法
在实现一个系统之前应当十分重视对系统需求的理解和交流,这一点已经是工人的准则。结构化分析方法产生于20世纪60年代,基本思想是对一个系统的功能构件进行分解,将一种更易于管理、更加确定的方法引入系统分析阶段。面向对象分析强调的则是对一个系统中对象的特征和行为地定义。
目前,国际上已经出现了多种面向对象的方法,例如 Peter Coad 和Edward Yourdon的 OOA和OOD方法、Booch的OOD方法、OMT(Object Modeling Technique,面向对象建模技术)方法及UML(Unified Modeling Language,统一建模语言)。这里将对 4 中方法进行简要介绍。
10.4.1 Peter Coad 和 Edward Yourdon 的 OOA 和OOD 方法
1、OOA
OOA模型由下列 5 个层次和 5个活动组成。
5 个层次: 主题层、对象类层、结构层、属性层和服务层
5 个活动:标识对象类、标识结构、定义主题、定义属性和定义服务。
在这种方法中定义了两种对象类之间的结构,一种称为分类结构,一种称为组装结构。
分类结构就是一般与特殊关系,一种是 is a关系。组装结构则反映了对象之间的整体与部分关系,
例如“ 计算机 ”对象由 “ 显示器 ”、“ 主机箱 ”、“ 键盘 ”和“ 鼠标 ”等对象组成,而“ 主机箱 ”对象又可由“ 主板 ”、“ CPU ”、“内存”、和“ 风扇 ”等对象组成。它实际上是一种 has a 的关系。
主题提供了控制读者在一段时间内考虑和理解模型范围的一种机制,在OOA 中将与某一主题有关的对象用主题框架围起来,其目的是减少读者理解系统的复杂程度。
OOA 在定义属性的同时,还要识别实例连接。实例连接是一个实例对象与另一个实例对象的映射关系(或者说是一种简单的对应关系)。
例如:一个公司有多个志愿,一个志愿只能在一家公司工作,那么“ 公司 ”类的实例与“ 职员 ”类的实例件就有 1 对多的实例连接关系。
OOA 在定义服务的同时要识别消息连接。当一个对象需要向另一个对象发送消息时,它们之间就存在消息连接。
需要强调的是,这 5 个活动不是必须顺序进行的,有些分析员喜欢先识别对象类,然后定义属性、服务,再识别结构和主题;有的则喜欢先识别对象、结构和主题,再定义属性和服务。总之 5 个活动都完成了, OOA 的模型就建立了。5 个层次并不是构成软件系统的层次,而是一旦建立了模型,就可以在 5 个层次上进行表示和复审。
2、OOD
OOA的 5 个层次和 5 个活动继续贯穿在 OOD过程中。
OOD 模型由 4 各部分和 4 个活动组成,
4 个活动是设计问题域部件、设计人机交互部件、设计任务管理部件及涉及数据管理部件。
(1)设计问题域部件。
OOA 的结果恰好就是 OOD的问题域部件,分析的结果在 OOD中可以被改动或增补,但基于问题域的总体组织框架是长时间稳定的。
(2)设计人机交互部件。
人机交互部件在上述结果中加入人机交互的设计和交互的细节,包括窗口和输出报告的设计。可以用原型来帮助实际交互机制的开发和选择。
(3)设计任务管理部件。
这部分主要是识别事件驱动的任务,识别时钟驱动的任务,识别优先任务和关键任务,识别协调者,审查每个任务并定义每个任务。
(4)设计数据管理部件。
数据管理部件提供了在数据管理系统中存储和检索对象的基本结构,其目的是隔离数据管理方案(如普通文件、关系数据库和面向对象数据库等)对其他部分的影响。
10.4.2 Booch 的 OOD 方法
Booch 认为软件开发是一个螺旋上升的过程,在螺旋上升的每个周期中有以下步骤。
(1)标识类和对象
(2)确定它们的含义
(3)标识它们之间的关系
(4)说明每一个类的界面和实现
Booch 的 OOD模型如图 所示:
除了类图、对象图、模块图和进程图外,Booch 的 OOD 中还是用了两种动态描述图,一种是刻画特定类实例的状态转换图,另一种是描述对象间事件变化的时序图。
10.4.3 OMT 方法
对象建模技术(Object Modeling Technique,OMT)是由J.Rumbaugh 等人提出的。
OMT 定义了三种模型,它们是对象模型、动态模型和功能模型,OMT用这三种模型来描述系统。
OMT 方法有 4 个步骤:
分析、系统设计、对象设计 和 实现。
OMT 方法的每一步都使用这三种模型,通过每一步对三种模型不断的精化和扩充。
1、对象模型、动态模型 和 功能模型
1)对象模型
对象模型描述系统中对象的静态结构、对象之间的关系、对象的属性、对象的操作。
对象模型表示静态的、结构上的、系统的“ 数据 ”特征。对象模型为动态模型和功能模型提供了基本框架。对象模型用包含对象和类的对象图来表示。
OMT 的对象模型中除了对象、类、继承外,还有一些其他的概念,下面介绍几个主要的概念。
(1)链(link)和关联(association)。
链表示实例对象间的物理或概念上的连接。例如在下表中,Joe Doe 为Simplex 公司工作,工资 2000元。关联描述具有公共结构和公共语义的一组链,例如关联 works-for 描述了一组某人为公司工作的链。实际上链是关联的一个实例。
链可以有属性,称为链属性,链属性表示关联中的性质。
下图给出了链、关联和链属性的一个实例。图中的实心圆是关联的阶(也成为了重数),阶指出一个类的多少个实例可以与所关联的类的一个实例相关。实心圆表示 0 或 多个,空心圆表示 0 或 1 个,没有园表示 1 个。
(2)泛化(Generalization)。
泛化是一个类与它的一个或多个细化种类之间的关系,即一般与特殊的关系。
被细化的类称为父类,每个细化的种类称为子类,子类可以继承父类的性质。
(3)聚集(Aggregation)。
聚集是一种整体与部分的关系,在这种关系中表示整体的对象与表示部分的对象关联。
(4)模块(module)
模块是组合类、关联和泛化的一种逻辑结构。模块给出了某个主题的视图。
2)动态模型
动态模型描述与时间和操作顺序有关的系统特征——激发事件、时间序列、确定事件先后关系以及事件和状态的组织。
动态模型表示瞬时的、行为上的、系统的“ 控制 ”特征。
动态模型用状态图来表示。每张状态图显示了系统中一个类的所有对象所允许的状态和事件的顺序。
3)功能模型
功能模型描述与值的变换有关的系统特征——功能、映射、约束和函数依赖。
功能模型用数据流图来表示。
对象模型、动态模型和功能模型之间具有下述关系。
(1)与功能模型的关系:对象模型展示了功能模型中的动作者、数据存储和流的结构,动态模型展示了执行加工的顺序。
(2)与对象模型的关系:功能模型展示了类上 的操作和每个操作的变量,因此它也表示了类之间的“ 供应者——客户 ”关系;动态模型展示了每个对象以及它接收时间和改变状态时所执行的操作。
(3)与动态模型的关系:功能模型展示了动态模型中未定义的不可分解的动作和活动的定义,对象模型展示了谁改变了状态和承受了操作。
2. OMT 的步骤
(1)分析。
分析是 OMT 方法的第一步,其目的是建立可理解的现实世界模型。分析从问题陈述入手,通过与客户的不断交互以及对现实世界背景知识的了解,对能反映系统的三个本质特征(对象类及他们之间的关系、动态的控制流和受约束的数据函数变换)进行分析,并构造出现实世界的模型。
分析模型必须简洁、明确地抽象目标系统必须做的事情,而不是如何做这些事。
模型中的对象应该是应用领域中的概念,而不是注入数据结构之类的计算机实现中的概念。一个好的模型应该被应用领域专家而不是程序员所理解和评价。分析模型不应包含任何与实现有关的考虑。
(2)系统设计
系统设计阶段确定整个系统的体系结构,形成求解和建立解答的高层次策略。系统设计人员必须形成的决策有:将系统分解成子系统,标识问题中固有的并发性,将子系统分配到处理器和任务,选择数据存储管理的手段,处理对全局资源的访问,选择软件中控制的实现,处理边界条件,设置折衷的优先级等。
(3)对象设计
在分析的基础上,对象设计阶段建立基于分析模型的设计模型,并考虑实现的细节。设计人员根据系统设计期间的策略把实现细节加入到设计模型中。对象设计的焦点是实现每个类的数据结构及所需的算法。分析模型中的对象类仍然是有意义的,但它们中都加入了数据结构和算法。
对象设计期间,设计者必须履行下列步骤:综合考虑三个模型以获得类的操作;设计实现操作的算法,优化数据访问路径,实现与外部交互的控制;调整类结构以增加继承,设计关联,确定对象表示,将类和关联包装到模块中。
(4)实现
实现阶段将对象设计阶段开发的对象类及其关系转换为特定的程序设计语言、数据库或硬件的实现。程序设计是开发周期中相对较小且机械的部分,因为在对象设计中已形成了所有重要的决策。目标语言在某种程度上影响设计决策,但设计不应该依赖于程序设计语言的细节。在实现期间,重要的应该有一些好的软件工程准则作指导。