Presentation is loading. Please wait.

Presentation is loading. Please wait.

第11章 面向对象实现 11.1 面向对象的程序设计语言 11.2 面向对象的程序实现特征 11.3 面向对象测试 11.4 组件技术简介.

Similar presentations


Presentation on theme: "第11章 面向对象实现 11.1 面向对象的程序设计语言 11.2 面向对象的程序实现特征 11.3 面向对象测试 11.4 组件技术简介."— Presentation transcript:

1 第11章 面向对象实现 11.1 面向对象的程序设计语言 11.2 面向对象的程序实现特征 11.3 面向对象测试 11.4 组件技术简介

2 11.1 面向对象的程序设计语言 11.1.1 面向对象语言的优点
11.1 面向对象的程序设计语言 面向对象语言的优点 编码相对软件生命期的各个阶段来说是最容易的,所以为数较多的初级程序员均可参加这一阶段的工作。但是在软件生命期中,程序是经常需要被阅读的,例如设计测试用例、排错、修改、功能扩充等都需要程序员或其他人员阅读程序。可以这样说,在软件开发过程中,读程序的时间比写程序的时间还要多,因此,如何能够更完整、更准确地表达问题域语义,使所开发出的程序易于阅读,使所开发的系统具有很强的可重用性和可维护性,选择一种什么样的语言作为开发工具就显得尤为重要。

3 面向对象设计的结果要转换为计算机系统可以识别的代码,既可以用面向对象语言,也可以用非面向对象语言实现。设计阶段设计的对象和关联最终都必须用具体的编程语言或数据库实现。使用OO语言来实现OO设计相对来说比较容易,因为语言的结构与设计的构造是相似的,OO语言支持对象、多态性和继承性。使用非OO语言需要特别注意和规定保留程序的OO结构,OO概念可以映射到非OO语言结构中,这只是一个表达方式的问题,不是语言能力的问题,因为编程语言最终要转换为机器语言,但OO语言良好的风格尤为突出。由于语言本身充分支持面向对象概念的实现,因此,编译程序可以自动把面向对象概念映射到目标程序中。使用非面向对象语言编写面向对象程序,则必须由程序员自己把面向对象概念映射到目标程序中。

4 例如人语言并不直接支持类或对象的概念,程序员只能在结构中定义变量和相应的函数(事实上,不能直接在结构中定义函数而是要利用指针间接定义)。所有非面向对象语言都不支持一般到特殊结构的实现,使用这类语言编程时要么完全回避继承的概念,要么在声明特殊化类时,把对一般化类的引用嵌套在它里面。

5 我们说选用面向对象语言还是非面向对象语言,这只是一个表达方式的问题,不是语言能力的问题。从原理上说,使用任何一种通用语言都可以实现面向对象概念。在传统的面向功能的方法学中,强调的是确定和分解系统功能,这种做法虽然是目标的最直接的实现方式,但由于功能是软件系统中最不稳定、最容易变化的方面,因而获得的程序往往难于维护和扩充。OO方法开发软件,其结构源于客观世界稳定的对象结构,与传统软件相比,软件本身的内部结构发生了质的变化,易重用性和易扩充性都得到提高。围绕对象来组织软件系统,可以自然地将现实世界模型映射到软件结构中。因此,使用面向对象语言,实现面向对象概念,远比使用非面向对象语言方便。当然,方便性也并不是决定选择何种语言的关键因素。

6 选择程序设计语言的关键因素,是语言的一致的表达能力、可重用性及可维护性。面向对象语言刻画客观系统较为自然,它具有:
① 识认性,系统中的基本构件可识认为一组可识别的离散对象; ② 类别性,系统具有相同数据结构与行为的所有对象可组成一类; ③ 多态性,对象具有惟一的静态类型和多个可能的动态类型; ④ 继承性,在基于层次关系的不同类中共享数据和操作等特点。 因此,能够更完整、更准确地表达问题域语义的面向对象语言的语法是非常重要的,这将会对系统带来下述几个重要优点。

7 1. 一致的表示方法 从前面章节的讲述中可以知道,面向对象开发基于不随时间变化的、一致的表示方法。这种表示方法应该从问题域到OOA,从OOA到OOD,最后从OOD到面向对象编程(OOP),始终稳定不变。一致的表示方法既有利于在软件开发过程中始终使用统一的概念,也有利于编程人员理解软件的各种配置成分。 我们以“自动饮料售货机”为例,说明面向对象开发基于不随时间变化的、一致的表示方法。对于“自动饮料售货机”这一实例,在问题域、OOA、OOD或是OOP的各个阶段所用到的概念都是一致的、不变的。如:退币杆、找零、熄灯、投币口、投币、饮料、饮料倒出等,这对于用户、分析人员、编程人员、测试人员、维护人员来说,无疑是有很大的帮助的。

8 2. 可重用性 软件的可重用性的好坏对于提高软件产品的质量和软件开发效率意义重大。为了能带来可观的商业利益,必须在更广泛的范围中运用重用机制,而不是仅仅在程序设计这个层次上进行重用。因此,在OOA、OOD直到OOP中都显式地表示问题域语义,其意义是十分深远的。随着时间的推移,软件开发组织既可能重用它在某个问题域内的OOA结果,也可能重用相应的OOD和OOP结果。

9 我们仍以“自动饮料售货机”为例,说明可重用性对于提高软件产品的质量和软件开发效率意义重大。假设该“自动饮料售货机”可提供汽水、洛神、红茶、可乐、奶昔等五种饮料,有关这五种饮料所实施的操作是相同的,因此,可以构造一个饮料类,然后由该类构造汽水、洛神、红茶、可乐、奶昔等五种不同的对象。这对于提高软件开发质量和软件开发效率具有重要的意义。

10 3. 可维护性 软件维护是一件极为复杂的事情,在软件的开发成本中约占到70%左右。为了降低维护成本,尽管人们反复强调保持文档与源程序一致的必要性,但是,在实际工作中很难做到交付两类不同的文档,并使它们保持彼此完全一致。特别是考虑到进度、预算、能力和人员等限制因素时,做到两类文档完全一致几乎是不可能的。因此,维护人员最终面对的往往只有源程序本身。

11 以ATM(自动取款机)系统为例,说明在程序内部表达问题域语义对维护工作的意义。假设在维护该系统时没有合适的文档资料可供参阅,于是维护人员人工浏览程序或使用软件工具扫描程序,记下或打印出程序显式陈述的问题域语义,维护人员看到“ATM”、“账户”和“现金兑换卡”等,这对维护人员理解所要维护的软件将有很大帮助。 因此,在选择编程语言时,应该考虑的首要因素,是在供选择的语言中哪个语言能最好地表达问题域语义。

12 面向对象语言的技术特点 面向对象语言借鉴了20世纪50年代诞生的人工智能语言LISP,引入了动态绑定的概念和交互式开发环境的思想;始于20世纪60年代的离散事件模拟语言SIMULA 67,引入了类的概念和继承机制;形成于20世纪70年代的Smalltalk语言。面向对象语言发展有两大方向,一是纯面向对象的语言,如 Smalltalk、EIFFEL、Java等语言;另一类是混合型面向对象语言,也就是在过程语言或其他语言中增加了类、继承等面向对象机制,如C++、Objective_C等语言。就两种形式的面向对象语言比较而言,纯面向对象语言更加适合面向对象方法研究和快速原型的实现;而混合型面向对象语言则更加注重于提高系统的运行速度,使传统使用结构化编程方式的程序员容易接受面向对象思想。

13 面向对象程序设计语言以对象为中心,对象是程序运行时的基本成分。面向对象程序设计语言中提供了类、继承等机制。面向对象的程序设计即为设计类及由类构造程序的方法和过程,用计算机对象模拟现实世界。成熟的面向对象的程序设计语言通常都提供丰富的类库和强有力的开发环境。

14 1. 支持类与对象概念的机制 面向对象语言都允许用户动态创建对象,并且可以用指针引用动态创建的对象。允许动态创建对象,就意味着系统必须处理内存管理问题,如果不及时释放不再需要的对象所占用的内存,动态存储分配就有可能耗尽内存,出现内存不足的问题。通常,对这类问题的解决方法有两种,一种是由语言的运行机制自动管理内存,即提供自动回收“垃圾”的机制;另一种是由程序员编写释放内存的代码。自动管理内存不仅方便而且安全,但是必须采用先进的垃圾收集算法才能减少开销。某些面向对象的语言(如C++)允许程序员定义析构函数(Destructor)。每当一个对象超出范围或被显式删除时,就自动调用析构函数。这种机制使得程序员能够方便地构造和唤醒释放内存的操作,却又不是采用垃圾收集机制。

15 2. 实现整体 - 部分结构的机制 实现整体 - 部分结构的机制有两种方法,一种是使用指针实现整体 - 部分结构,另一种是使用独立的关联对象实现整体 - 部分结构。一般来说,通过增加内部指针可以方便地实现关联,使用指针是最容易的实现方法。但是,大多数现有的面向对象语言并不显式支持独立的关联对象。

16 3. 实现一般 - 特殊结构的机制 实现一般 - 特殊结构的机制,包括实现继承的机制和解决名字冲突的机制。所谓解决名字冲突,是指在支持多重继承的语言中,处理在多个基类中可能出现的重名问题。通常,有些语言拒绝接受有名字冲突的程序,另一些语言提供了解决冲突的协议。无论使用何种语言,程序员都应该尽力避免出现名字冲突。

17 4. 实现属性和服务的机制 实现属性的机制应该着重考虑的几个因素:支持实例连接的机制;属性的可见性控制;对属性值的约束。对于实现服务的机制来说,主要应该考虑下列因素:支持消息连接的机制;控制服务可见性的机制;动态联编。 所谓动态联编,是指应用系统在运行过程中,当需要执行一个特定服务的时候,选择(或联编)实现该服务的适当算法的能力。动态联编机制使得程序员在向对象发送消息时拥有较大自由,在发送消息前,无须知道接收消息的对象当时属于哪个类。

18 5. 类型检查 按照编译时进行类型检查的严格程度,程序设计语言可以分为三种类型:弱类型、强类型和混合型。弱类型:语言仅要求每个变量或属性隶属于一个对象。强类型:语法规定每个变量或属性必须准确地属于某个特定的类。面向对象语言包含不同类型的语言,例如,Smalltalk实际上是一种无类型语言(所有变量都是未指定类的对象);C++则是强类型语言。混合型语言:为了提高操作的效率,甚至允许属性值不是对象而是某种预定义的基本类型数据(如整数、浮点数等),如C++,Objective_C等。

19 强类型语言主要有两个优点:有利于在编译时发现程序错误;增加了优化的可能性。通常使用强类型编译型语言开发软件产品,使用弱类型解释型语言快速开发原型。一般来说,强类型语言有助于提高软件的可靠性和运行效率,现代的程序语言都是强类型的,大多数理论支持强类型检查。

20 6. 类库 目前,基本所有的面向对象的程序设计语言都提供一个实用的类库。某些语言本身并没有规定提供什么样的类库,而是由实现这种语言的编译系统自行提供类库。有了类库,程序员可以重用许多软构件,不必重新编写,这为实现软件重用带来很大方便。 类库中通常包含实现通用数据结构的类,例如,动态数组、表、队列、栈和树等,通常把这些类称为包容类。类库中还包含了实现各种关联的类。 更完整的类库通常还提供了接口类和图形库。接口类是独立于具体设备的(例如,输入/输出流),图形库是用于实现窗口系统的用户界面类的一个相对独立的库。

21 7. 效率 某些早期的面向对象语言是解释型的而不是编译型的。许多人认为这些语言的主要缺点是效率低。当今的面向对象语言都拥有完整类库,类库中提供了更高效的算法和更好的数据结构,与非面向对象语言相比,能得到更快运行的代码。例如,库中已经提供了算法先进、代码可靠的一类数据结构,程序员再不必编写像实现哈希表或平衡树算法的代码了,因此,提高了编程效率和运行效率。

22 面向对象语言在运行时使用动态联编实现多态性,这似乎需要在运行时查找继承树,以得到定义给定操作的类。这也是人们认为面向对象语言效率低的另一个理由。当今的绝大多数面向对象语言都优化了这个查找过程,从而实现了高效率查找。只要在程序运行时始终保持类结构不变,就能在子类中存储各个操作的正确入口点,从而使得动态联编成为查找哈希表的高效过程,不会由于继承树深度加大或类中定义的操作数增加而降低效率。

23 8. 永久保存对象 在一个程序都对数据进行处理时,希望数据能够长时间保存下来,以备后用。保存数据需要提供某种保存数据的方法。长期保存数据主要有两个原因: ① 为实现在不同程序之间传递数据,需要保存数据; ② 为恢复被中断了的程序的运行,首先需要保存数据。

24 对于不同面向对象语言,长期保存数据的方法也不同。有些面向对象语言(例如C++)没有提供直接存储对象的机制,这些语言的用户必须自己管理对象的输入/输出,或者购买面向对象的数据库管理系统。有些面向对象语言(例如Smalltalk)把当前的执行状态完整地保存在磁盘上。还有一些面向对象语言提供了访问磁盘对象的输入/输出操作。

25 通过在类库中增加对象存储管理功能(例如EIFFEL语言采用的策略),可以在开发环境中提供对象存储管理功能,而且还不改变语言定义或不增加关键字。然后,可以从“可存储的类”中派生出需要永久保存的对象,该对象自然继承了对象存储管理功能。如果能使程序设计语言语法与对象存储管理语法实现无间隙集成,则是最理想的。

26 9. 类模板 在实际的应用系统开发中,经常用函数、类等软件元素处理不同类型的数据(对象),但是,对它们的数据元素所进行的基本操作都是相同的。例如,对于一个向量(一维数组)类来说,不论是整型向量,浮点型向量,还是其他任何类型的向量,针对它的数据元素所进行的基本操作都是相同的(例如,插入、删除、检索等)。在这种情况下,如果程序语言提供一种能抽象出这类共性的机制,则对减少冗余和提高可重用性大有好处。

27 所谓参数化类,就是使用一个或多个类型去参数化一个类的机制,有了这种机制,程序员就可以先定义一个参数化的类模板(即在类定义中包含以参数形式出现的一个或多个类型),然后把数据类型作为参数传递进来,从而把这个类模板应用在不同的应用程序中,或用在同一应用程序的不同部分。EIFFEL语言中就有参数化类,C++语言也提供了类模板。Visual Basic和Visual C++提供了函数模板和类模板。

28 10. 开发环境 在软件工程学中,方法和工具之间是相互依赖的关系,方法是工具研制的先导,工具是方法的实在体现。软件开发环境是指在计算机的基本软件基础上,为了支持软件开发而提供的一组工具软件系统。软件工具和软件工程环境对软件生产率有很大影响。由于面向对象程序中继承关系和动态联编等引入的特殊复杂性,面向对象语言所提供的软件工具或开发环境就显得尤为重要了。一般来说,面向对象语言所提供的开发环境,至少应该包括下列一些最基本的软件工具:编辑程序、编译程序或解释程序、浏览工具和调试器等。

29 编译程序或解释程序是最基本、最重要的软件工具。编译与解释的差别主要是速度和效率不同。利用解释程序解释执行用户的源程序,虽然速度慢、效率低,但调试比较方便、灵活。编译型语言具有良好的优化功能,并且生成目标代码效率很高,适于用来开发正式的软件产品。有些面向对象语言(例如 Objective_C)不但提供了编译程序,还提供了解释工具,这样给软件开发人员带来很大方便。

30 有些面向对象语言虽然提供编译程序,但是,源程序到目标代码翻译是间接的。它先把用户源程序翻译成一种中间语言程序,然后再把中间语言程序翻译成目标代码。像这类的编译程序,就很有可能使调试器不能理解原始的源程序。因此,使用调试器时,首先应该弄清楚它是针对原始的面向对象源程序,还是针对中间代码进行调试的。如果是针对中间代码进行调试的,则会给调试人员带来许多不便。除此之外,查看属性值和分析消息连接也是面向对象的调试器应该具备的功能。 在开发大型系统的时候,还可能需要系统构造工具和变动控制工具。因此应该考虑语言本身是否提供了这种工具,或者该语言能否与现有的这类工具很好地集成起来。

31 选择面向对象语言 总体来说,在使用面向对象的软件开发过程中,OO语言明显优于非OO语言,因此,除了在很特殊的应用领域,如:对程序的执行时间和使用空间都有很严格限制的情况;需要产生任意的甚至非法的指令序列;体系结构特殊的微处理器等。开发人员一般应该选择面向对象的程序设计语言,但是,目前面向对象的程序设计语种类繁多,究竟应该选择何种语言更利于系统开发和维护呢?在充分考虑到程序设计语言特点(如应用领域、算法与计算的复杂性、数据结构的复杂性、效率等)的同时,还应该着重考虑以下一些实际因素。

32 1. 未来能否占主导地位 语言在未来能否占主导地位,是否具有很强的生命力,对于软件生存期具有相当重要的作用。因为,软件投入运行后,其维护和功能扩充是经常性的工作,如果在若干年以后,你所使用的面向对象的程序设计语言仍占主导地位,那么,你所开发的产品在若干年后仍然具有很强的生命力。否则,若干年后,其维护或功能扩充就很难实施了,你所开发的产品就会自动退役。

33 究竟如何来选择呢?通常情况下,就是依据目前该语言占有的市场份额,以及专业书刊和学术会议上所做的分析、评价。这样,人们往往能够对未来哪种面向对象语言将占据主导地位做出预测。当然,最终决定选用哪种面向对象语言的实际因素,往往是诸如成本之类的经济因素而不是技术因素。

34 2. 可重用性 采用面向对象方法开发软件的基本目的,是通过重用提高软件质量和软件生产率,增强系统的可维护性。面向对象语言的主要优点是能够最完整、最准确地表达问题域语义,因此,在开发系统时,应该优先选用面向对象语言。

35 3. 类库和开发环境 语言、开发环境和类库是决定可重用性的三个因素。可重用性除了依赖于面向对象程序语言本身以外,同时还依赖于开发环境优劣和类库内容的丰富程度。只有语言、开发环境和类库这三个因素综合起来,才能共同决定可重用性。 考查程序语言的时候,不但应该考查是否提供了类库,更重要的是考查类库中提供了哪些有价值的类。随着类库的日益成熟和丰富,会给开发应用系统带来很大的方便,需要开发人员自己编写的代码将越来越少,以致会有事半功倍或更高的效率。

36 目前较为流行的OO程序设计语言和工具大致可以分为两类,一类是传统OO语言和工具:Smalltalk、C++、Object Pascal、Java等;另一类是高级OO语言(OO-4GL)和工具:Visual Basic、PowerBuilder、Delphi、Power Objects、Developer/2000、Visual Foxpro、Javascript等。用户在选择具体的语言作为开发工具时,除要考虑上述的几个方面外,还应该考虑的其他因素有:对用户学习面向对象分析、设计和编码技术所能提供的培训服务;在使用这个面向对象语言期间能提供的技术支持;能提供给开发人员使用的开发工具、开发平台;对机器性能和内存的需求;集成已有软件的容易程度;软件的规模;软件的可移植性;软件的应用领域等。

37 11.2 面向对象的程序实现特征 程序是软件设计的自然结果,程序的质量主要取决于设计的质量,根据设计的要求选择了程序设计语言之后,编程风格在很大程度上影响着程序的可读性、可测试性和可维护性。保证程序质量的重要方法是有良好的程序设计风格。对面向对象实现来说,良好的程序设计风格也是非常重要的,它不仅能够减少系统维护或扩充所带来的系统开销,而且更有助于在新项目或工程中重用已有的程序代码。因此,良好的面向对象程序设计风格,既要遵循传统的结构化程序设计风格和准则,同时也要遵循为适应面向对象方法所特有的概念(例如,继承性)而必需的一些新的风格和准则。

38 提高可重用性 提高软件的可重用性是面向对象方法的一个主要目标。软件重用是指在两次或多次不同的软件开发过程中重复使用相同或相似软件元素的过程。软件元素包括程序代码、测试用例、设计文档、设计过程、需求分析甚至知识领域等多个层次。在编码阶段主要考虑代码重用的问题,代码级的重用是最简单,也是最流行的,是迄今为止研究最深入、应用最广泛的重用技术。传统上,代码级重用部件的主体是子程序(函数或过程)。现在,由于基于对象和面向对象的程序设计语言及相应开发环境的发展,代码级重用部件的内容更加丰富,不仅包括结构化程序设计语言中的函数或过程,同时也包括面向对象程序设计语言中的程序包和类。

39 一般说来,代码重用可分为两种形式,一种是本项目内的代码重用,称为内部重用;另一种是新项目重用已有项目的代码,称为外部重用。内部重用主要是找出本项目设计中相同或相似的部分,然后利用继承机制共享它们。要做到外部重用,即一个项目重用另一项目的代码,就要求开发人员必须具有重用的设计思想,做到反复考虑,精心设计。虽然为实现外部重用与内部重用需要考虑的范围不同,但是有助于实现这两类重用的程序设计准则却是相同的。下面介绍主要的准则。

40 1. 提高方法的内聚,减小方法的规模 一个方法(即服务)应该只完成单个功能,这样的方法是高内聚的。如果某个方法涉及两个或多个不相关的功能或规模大的方法,则应该把它分解成几个更小的方法。 2. 保持方法的一致性 实现代码重用的一个重要因素是应该保持方法的一致性。一般来说,功能相似的方法应该有一致的名字、参数特征(包括参数个数、类型和次序)、返回值类型、使用条件及出错条件等。这样有助于实现代码重用。

41 3. 把策略与实现分开 方法从所完成的功能来看有两种类型:策略方法和实现方法。策略方法负责做出决策,提供变元,并且管理全局资源;实现方法只负责完成具体的操作,但却没有执行这个操作的决定权,也不知道为什么执行这个操作。 策略方法不直接完成计算或实现复杂的算法,只是检查系统运行状态,并处理出错情况。该方法通常紧密依赖于具体应用,这类方法易编写、易理解。

42 实现方法仅仅针对具体数据完成特定处理,通常用于实现复杂的算法。实现方法既不制定决策,也不管理全局资源。实现方法如果在执行过程中发现错误,只返回执行状态而不处理出错情况。由于实现方法是自含式算法,与具体应用相对独立,因此,容易在其他应用系统中重用,具有较高的可重用性。 在编程时分清策略方法和实现方法,不要把它们放在同一个方法中。应该把算法的核心部分放在一个单独的具体实现方法中。为此需要从策略方法中提取出具体参数,作为调用实现方法的变元,才能提高方法可重用性。

43 4. 采用全面覆盖技术 在编程时,对系统全面考虑,写出覆盖整个系统所有可能的方法。而不能仅仅针对当前需要的功能写方法。例如,如果在当前应用中需要写一个获取表中第一个元素的方法,则至少还应该为获取表中最后一个元素再写一个方法。一个方法不仅能处理正常值,而且还能够对异常情况(如空值、极限值及界外值等)作出有意义的响应。

44 5. 降低方法的耦合度 降低方法与外界的耦合程度的一项主要措施是在方法中尽可能少使用或不使用全局信息。

45 6. 充分地利用继承机制 使用继承机制可实现共享和提高重用性,是面向对象程序设计的主要途径。 (1) 使用子过程。把公共的代码分离出来,构成一个被其他方法调用的公用方法,是实现代码复用最常用、最简单的方法。通常可以在基类中定义这个公用方法,供派生类中的方法调用。

46 (2) 分解因子。提高相似类代码可重用性的另一个有效途径,是从不同类的相似方法中分解出公共代码和不同的代码(即因子)。把公共代码作为公用方法,定义在一个抽象基类(额外的)中;把不同的因子作为名字相同而算法不同的方法,定义在不同类中,并被这个公用方法调用。把这种分解与面向对象语言提供的多态性机制结合起来,抽象基类中定义的公用方法可由派生类来继承,在新子类中只需编写其特有的代码,由于增添新子类编写的代码减少了,显然为增添新子类所需付出的工作量也就明显降低了。

47 (3) 使用委派。当确实存在一般 - 特殊关系时,利用继承关系,子类可以继承父类的所有方法和属性。但是,当逻辑上不存在一般 - 特殊关系时,为重用已有的代码,可以利用委派机制。要恰当地使用继承机制,否则会降低程序的可理解性、可修改性和可扩充性。

48 (4) 把重用代码封装在类中。解决同一类应用问题时,程序员往往希望重用其他方法编写的程序代码。那么就将被重用的代码封装在类中,这样使用起来比较安全,也是程序员应该提倡的编程风格。例如,如果开发一个应用数学系统时,发现有现成的实现迭代法的商品软件包,程序员想重用这个算法,于是先定义一个迭代法类,然后把这个商品软件包的功能封装在该类中即可。

49 提高可扩充性 前面所述的提高可重用性的准则,同样能提高程序的可扩充性。下面将介绍有助于提高程序的可扩充性的面向对象程序设计准则。 1. 封装实现策略 为了提高类中数据结构或算法可修改性,应该把类中描述属性的数据结构以及修改属性的算法等实现策略封装起来,只提供公有的接口供外界访问。

50 2. 方法的功能应该单一 一个方法应该高内聚低耦合,包含对象模型中的有限内容。如果用一个方法遍历多条关联链,将使方法过分复杂,既降低了可理解性,又影响了可修改性和可扩充性。 3. 避免使用多分支语句 在实施系统开发过程中,可以利用DO-CASE语句测试对象的内部状态,避免使用多分支语句来根据对象类型选择应有的行为,否则在增添新类时将不得不修改原有的代码。一般来说,应该合理地利用多态性机制,根据对象当前类型,自动决定应有的行为。

51 4. 精心选择和定义公有方法 公有方法是对外公用的接口,私有方法是仅在类内使用的方法,通常利用私有方法来实现公有方法。一般来说,公有方法的修改代价要比私有方法大得多,因为对公有方法的修改常常会引起许多其他类的修改,而私有方法的删除、增加或修改所涉及的面要小得多(仅在类内),因此代价也比较小。为提高可修改性,降低维护成本,必须精心选择和定义公有方法。

52 提高健壮性 提高健壮性,也是程序员在编写实现方法的代码时应该考虑的一个重要方面。通常需要在健壮性与效率之间做出适当的折衷。健壮性对任何一个实用软件都是不可忽略的质量指标。遵守下述准则将有助于提高健壮性。 1. 增强系统的容错功能 系统应该具备容错功能,即具有处理用户操作错误的能力。当用户操作失误或输入数据出错时,不会引起程序运行中断,更不应该造成“死机”,而应该对操作进行检查,发现了的错误,必须给出有关的提示信息,并允许用户再次输入或正常退出。

53 2. 检查参数的合法性 对软件系统中所使用的方法或过程,尤其是公有方法或过程,应该着重检查其参数的合法性,不能因为用户在使用公有方法或过程时,违反参数的约束条件而引发程序运行中断或造成系统“死机”现象。

54 3. 不要预先确定限制条件 预先确定限制条件对所开发的系统往往会产生一些不良的后果。特别是在设计阶段,往往很难准确地预测出应用系统中使用的数据结构的最大容量需求。当系统中使用的数据结构的容量大于预先确定的限制条件时,系统有可能引发一连串的问题。因此不应该预先确定限制条件。如果有必要和可能的条件下,则应该使用动态内存分配机制来创建数据结构,当然,该数据结构是未预先确定限制条件的。

55 4. 先测试后优化 为了提高健壮性,往往需要增加大量的代码,这就或多或少地影响了系统的执行效率。因此,就需要在效率与健壮性之间做出合理的折衷。一般来说,首先根据应用程序的特点,确定需要着重测试的部分(例如,最坏情况出现的次数及处理时间等),进行测试。然后为提高性能,确定着重优化的关键部分。此外在选择算法时,要综合考虑内存需求、速度以及实现的简易程度等因素,选择出适当的算法。

56 11.3 面向对象测试 OO软件的单元测试 在面向对象的软件开发中,“封装”导致了类和对象的定义,这意味着类和类的实例(对象)包装了属性(数据)和处理这些数据的操作(也称为方法或服务)。其核心是“对象”,不存在传统软件开发中的“单元”(或者说单元的概念改变了)。也就是说,封装起来的类和对象是最小的可测试单元。一个类可以包含一组不同的操作,而一个特定的操作也可能定义在一组不同的类中。因此,面向对象的软件的单元测试与传统测试方法不一样,它的含义发生了很大变化。

57 面向对象的软件的单元测试,不是独立地测试单个操作,而是把所有操作都看成类的一部分,全面地测试类和对象所封装的属性和操纵这些属性的操作整体。具体地说,在OO的单元测试中不仅要发现类的所有操作中存在的问题,还要考查一个类与其他的类协同工作时可能出现的错误。现以实例说明:在一个类层次中,操作A在超类中定义并被一组子类继承,每个子类都可使用操作A,但是A调用于类中定义的操作并处理子类的私有属性。由于在不同的子类中使用操作A的环境有所不同,因此有必要在每个子类的语境中测试操作A。这就是说,当测试面向对象软件时,传统的单元测试方法是不可用的,我们不能再独立地对操作A进行测试。

58 OO软件的集成测试 传统的集成测试是采用自顶向下或自底向上或二者混合的两头逼近策略,通过用渐增方式集成功能模块进行的测试。但是由于面向对象程序没有层次的控制结构,相互调用的功能也是分散在不同的类中,类通过消息的相互作用申请和提供服务,所以这种集成测试的策略就没有意义了。此外,由于面向对象程序具有动态性,程序的控制流往往难以确定,因此只能做基于黑盒方法的集成测试。 OO集成测试主要关注于系统的结构和内部的相互作用。面向对象软件的集成测试有两种方法。

59 1. 基于线程的测试(Thread-based Testing)
基于线程的测试是指把响应系统的一个输入或一个事件所需要的一组类集成起来进行测试。应当分别集成并测试每个线程,同时为了避免产生副作用再进行回归测试。

60 2. 基于使用的测试(Use-based Testing)
基于使用的测试首先测试几乎不使用服务器类的那些类(称为独立类),把独立类都测试完之后,接着测试使用独立类的最下层的类(称为依赖类)。然后,根据依赖类的使用关系,从下到上一个层次一个层次地持续进行测试,直至把整个软件系统测试完为止。 除了上述两种测试方法,集群测试是面向对象软件集成测试的一个步骤。为了检查一群相互协作的类,用精心设计的测试用例,力图发现协作错误。通过研究对象模型可以确定协作类。 为减少测试工作的工作量,在进行集成测试时,可参考类关系图或实体关系图,确定不需要被重复测试的部分,从而优化测试用例,使测试能够达到一定的标准。

61 OO软件的确认测试与系统测试 通过对OO软件单元测试和集成测试,仅能确认软件开发的功能是正确的,但是不能确认在实际运行时,它是否满足用户要求,是否大量存在与实际使用条件下的各种应用相矛盾的错误,为此,在完成上述测试活动后,还必须经过规范的确认测试和系统测试。 面向对象软件的确认测试或系统测试,与传统的确认测试一样,通过设计测试用例,主要检查用户界面和用户可识别的输出,不再考虑类之间相互连接的细节。测试人员应该认真研究动态模型和描述系统行为的脚本,为系统的输入信息设计出错处理的通路,模拟错误的数据和软件界面可能发生的错误,设计出合理的测试用例。

62 目前,面向对象软件的测试用例的设计方法,还处于研究、发展阶段。1993年,Berard提出了指导OO软件测试用例设计的方法,其要点如下:
设计测试用例 目前,面向对象软件的测试用例的设计方法,还处于研究、发展阶段。1993年,Berard提出了指导OO软件测试用例设计的方法,其要点如下: ● 每一个测试用例都要有一个惟一的标识,并与被测试的一个或几个类相关联起来; ● 每个测试用例都要陈述测试目的; ● 对每个测试用例要有相应的测试步骤,包括被测对象的特定状态,所使用的消息和操作,可能产生的错误及测试需要的外部环境。 ● 与传统软件测试(测试用例的设计由软件的输入— 处理— 输出或单个模块的算法细节驱动)不同,面向对象测试关注于设计适当的操作序列以检查类的状态。

63 1. OO概念对测试用例设计的影响 封装性和继承性是类的重要特性,这给面向对象的软件开发带来很多好处,同时它又对面向对象的软件测试带来负面影响。 类的属性和操作是被封装的,而测试需要了解对象的详细状态。同时,需要考虑当改变数据成员的结构时,是否影响了类的对外接口,是否导致相应外界必须改动。例如,强制的类型转换会破坏数据的封装性,请看下面的这段程序:

64 class Hd {private: int a=1; char *h="Hd";} class Vb {public: int b=2; char *v="Vb";} Hd p; Vb *q=(Vb *)&p;

65 则其中,p的数据成员可以通过q被随意访问。
此外,继承不会减少对子类的测试,相反,会使测试过程更加复杂化。因此,继承也给测试用例的设计速度带来负面影响。当父类与子类的环境不同时,父类的测试用例对子类没有什么使用价值,必须为子类设计新的测试用例。

66 在设计面向对象的测试用例时应注意以下三点。
(1) 继承的成员函数需要测试。对于在父类中已经测试过的成员函数,根据具体情况仍需在子类中重新测试。一般在下述两种情况下要对成员函数重新进行测试: ● 继承的成员函数在子类中有所改动; ● 成员函数调用了改动过的成员函数。

67 (2) 子类的测试用例可以参照父类。例如,有两个不同的成员函数的定义如下:
father::B()中定义为 if (value<0) message("less"); else if (value==0) message("equal"); else message("more"); son::B()中定义为 else if (value==0) message("It is equal"); else {message("more"); if (value==99) message ("Luck");} 在原有的测试上,对son::B()的测试只需作如下改动:将value==0的测试结果期望改动,并增加value==99这一条件的测试。

68 (3) 设计测试用例时,不但要设计确认类功能满足的输入,而且还应有意识地设计一些被禁止的例子,确认类是否有不合法的行为产生。

69 2. 类测试用例设计 类测试是类生存期中的初始测试阶段。类一般是一些单独的部件,可以用于不同的应用软件中。这就要求每个类都必须是可靠的,并且不需要了解任何实现细节就能复用。类的测试既可以使用传统的白盒测试方法,也可以使用黑盒测试方法。一般来说,在设计测试用例时,可参照下列步骤: (1) 根据OOD分析结果,选定检测的类,并仔细分出类的状态和相应的行为,以及成员函数间传递的消息和输入输出的界定。

70 (2) 确定覆盖标准。 (3) 利用结构关系图确定待测试类的所有关联。 (4) 构造测试用例,确认使用什么输入来激发类的状态,使用类的什么服务,期望产生什么行为。

71 下面介绍两种常用的类测试用例设计方法。 1) 基于故障的测试用例设计 基于故障的测试(Fault-based Testing)与传统的错误推测法类似,通过对OOA/OOD模型的分析,首先推测软件中可能有的错误,然后设计出最可能发现这些错误的测试用例。例如,软件工程师经常在问题的边界处犯错误,因此,在测试(计算平方根)操作(该操作在输入为负数时返回出错信息)时,应该着重检查边界情况:一个接近零的负数和零本身。其中“零本身”用于检查程序员是否犯了如下错误:

72 把语句 if( x>=0)calculate=sqr(x);
再如:程序中将 if(strncmp(str1,str2,strlen(str1))) 误写成 if(strncmp(str1,str2,strlen(str2))) 那么如果在测试用例中使用的数据str1和str2长度相同,就无法检测出错误。 为了推测出软件中可能有的错误,测试人员应该认真研究分析模型和设计模型,还得依靠测试人员的经验和直觉。如果推测得比较准确,则使用基于故障的测试方法能够用较少的工作量发现大量错误, 否则不然。

73 2) 基于用例的测试用例设计 基于故障的测试用例有一个很突出的缺点:当功能描述是错误的或子系统间交互存在错误时,基于故障的测试用例就无法发现错误。而基于用例的测试用例更关心的是用户想做什么而不是软件想做什么,通过用例获取用户要完成的功能,并以此为依据设计所涉及的各个类的测试用例。更具体地说,先搞清楚用户想实现哪些功能?然后去寻找要完成的这些功能,需要哪些类参与,从功能出发,对所确定的这些类及其子类分别设计类测试用例。

74 3. 类间测试用例设计 在集成面向对象系统阶段,必须对类间协作进行测试,因此测试用例的设计变得更加复杂。通常可以从OOA的类 — 关系模型和类 — 行为模型导出类间测试用例。测试类协作可以使用随机测试方法和划分测试方法,以及基于情景的测试和行为测试来完成。

75 (1) 多个类的划分测试方法。多个类的划分测试方法有三种:基于状态的划分,基于属性的划分和基于功能的划分。根据类操作改变类状态的能力来划分类操作,称为基于状态的划分法;根据类操作使用的属性来划分类操作,称为基于属性的划分法;根据类操作所完成的功能来划分类操作,称为基于功能的划分法。另外,多类测试还应该包括那些通过发送给协作类的消息而被调用的操作。根据与特定类的接口来划分类操作又是一种划分测试方法。

76 (2) 从行为模型导出测试用例。动态行为模型是由几个状态转换图组成的。根据类的状态图,可以设计出测试该类以及类间的动态行为的测试用例。同时,为了保证该类的所有行为都能被测试,还可以利用状态图设计更多测试用例。 面向对象方法学把分析、设计和实现很自然地联系在一起了。虽然面向对象设计原则上不依赖于特定的实现环境,但是实现结果和实现成本却在很大程度上取决于实现环境。因此,直接支持面向对象设计范型的面向对象程序语言、开发环境及类库,对于面向对象实现来说是非常重要的。

77 11.4 组件技术简介 11.4.1 组件的概念及特点 1. 组件(Component)的概念
11.4 组件技术简介 组件的概念及特点 1. 组件(Component)的概念 组件就像我们日常生活中用到的汽车零件或计算机零件。例如:我们的手指就是人体的组件,假若缺少了一个指头,则人体就不再是完整的实体,所以手指是构成完整人体的基本组成部分,是不可缺少的。 “组件”一词只是用来指明整体(the whole)与部分(the part)之间的关系。例如:一间教室是一个整体,而教室是一栋教学楼的组成部分,所以教室是教学楼的组件。同理,一所学校是一个整体,而学校里的教学楼则是学校的组件,如图11.1所示。

78 图11.1 组件示意

79 2. 组件的特点 (1) 面向用户。组件应用可视、非程序化开发工具建立应用系统模型。组件开发工具由图形用户界面支持,不要求使用者具备计算机编程能力,并且这种支持贯穿从组件开发直到最终应用系统开发的全过程。 (2) 适应性。组件应具有全面、个性化、可调节的适应能力。由于用户所属行业、规模和生产类型的多样化,以及企业组织和业务流程经常性的变化,因此要求软件供应商或开发者所提供的软件能适应企业个性化的要求。

80 (3) 对业务逻辑的封装。对业务逻辑封装的规划,确定了组件的边界和接口特性。良好的规划可以使组件和其功能与某一具体的应用系统之间相对独立,组件可以独立开发和分开测试。
(4) 开放性。组件不依赖于用户的类型和规模,可以在多数据库系统、多操作系统平台上运行。系统中所有的单元均不依赖于某一种数据库系统。例如,借助于开发工具所开发的组件和应用系统中,数据可以在异构数据库之间进行转换。

81 (5) 连续性。在整个开发过程中,由开发工具实现企业和应用系统开发之间的联系。从企业设计到过程组织和数据组织,直到最终应用系统的每一个步骤,使用者都是由相应的开发工具支持的。由开发工具开发的组件可以被多次重用和组合,所以使用者只需要精通较少的基本单元,如一个数据登录界面可以被用于一个任务的输入,也可以被用于查看所有过程的信息或对数据表格内容的快速浏览。

82 (6) 可重用性。 组件化开发有利于软件企业的经验和技术的积累。 组件可以很好地重用,使软件企业或开发商大大减少后续开发、改进和功能扩充所需的投入和费用。组件开发技术使业务逻辑封装在规划好的组件单元中,当面对不同用户需求时,只需更改相应的组件,通过事先定义好的组件产品,很快完成系统集成。此外,使用组件管理可以方便系统版本的升级。当现有系统不能满足要求时,一般只要对现有系统进行再开发,而不必抛弃现有系统重新开发。

83 (7) 工具支持。为了简化组件的开发和应用,使用者必须由软件工具支持。
● 设计模板——用于信息模型和企业模型设计。 ● 工具支持——用于组件的非程序化开发、转换和适应。 ● 目录化——用于组件库和组件的分类。为了组件的重用,仅仅将组件集合到一个目录中是不够的,重要的是对组件进行分类,这样便于开发人员在开发时选用合适的组件。

84 组件分类及开发工具 1. 组件的分类 1) 按功能来分 (1) 核心组件——应用系统开发中基本的、必不可少的组成单元,如数据表格组件、数据登录界面组件、算法组件等。 (2) 辅助组件——为了扩展和增强应用系统的功能而开发的组件。它不是每一个应用系统所必需的,可以根据用户的要求安装或扩展,如决策支持系统(DSS)组件、模拟支持系统(SSS)组件、Internet组件等。

85 2) 按组成来分 (1) 基本组件——每一个组件中只含有一个组件,如一个数据表格及一个数据登录界面组件。 (2) 组合组件——由两个或两个以上的基本组件组成的组件,也可称为部件,如一个算法组件中常常包含两个或更多的基本组件。

86 3) 按组件开发工具所生成的结果来分 (1) 企业系统设计组件(USE组件)——主要功能为建立企业组织结构模型。 (2) 业务过程计划组件(GPP组件)——主要功能为创建企业业务流程模型。 (3) 数据库表格组件(DBF组件)——主要功能为创建各种数据库表格。

87 (4) 算法组件(MTH组件)——主要功能是进行各种算法运算,进行数据处理。
(5) 数据登录界面组件(MSK组件)——主要功能为创建各种数据表格界面,完成数据的录入、输出、更改、删除。 (6) 辅助功能组件——主要功能是实施各种辅助功能,如可视化、模拟等。

88 2. 组件开发工具 组件技术应用的前提是具有一定数量的组件或组件库,而组件本身也是需要开发的。组件开发工具是组件开发的前提,目前尚未形成被广泛接受的通用标准的开发工具,但是先进的软件开发工具至少应具备如下的特点: (1) 应用面向对象的技术(OO技术); (2) 应用第四代程序设计语言(4GL)设计; (3) 采用关系型数据库管理系统(RDBMS); (4) 具有图形用户界面(GUI),不要求应用系统开发人员和使用者具备程序设计语言的能力; (5) 支持Internet和Intranet。

89 组件开发原则与组件管理 1. 组件开发原则 为了适应后续模型开发和最终应用系统开发的可重用性和可柔性组件的要求,在应用组件开发中应该注意以下几个方面的原则: (1) 通用性原则——组件开发时应该充分考虑到应用系统开发中的通用性要求。如在数据库表格组件的开发中,表格的结构、数据的定义等应该尽可能满足应用系统开发时的通用性要求。

90 (2) 广义性原则——应用系统开发应具有不同功能、不同种类的组件,以满足不同用户类型、不同生产方式和不同用户规模的用户个性化要求。
(3) 标准化原则——由于所使用的开发工具不同,要做到组件的完全标准化是不可能的。这里的“标准化”是指在采用同一开发工具的前提下,开发的组件尽可能符合一个统一的标准。 (4) 较高的柔性原则——在推荐模型和应用系统开发过程中,组件既可以直接被重用,也可以在进行适当修改后被重用。

91 (5) 结构简洁原则——组件应该具备简洁的结构,以便在推荐模型开发和应用系统开发中引用和进行适应性改造,同时方便组件管理。
(6) 功能、参数的清晰描述——对组件的功能和参数应尽可能给予清楚的描述,以便于管理。 (7) 友好的用户界面。

92 2. 组件的管理 1) 组件管理的目的 由于一个应用系统的开发过程中将会使用大量的组件,因此,组件管理是应用组件技术开发应用系统的一个不可忽视的方面。组件管理的目的在于建立统一、完整的组件档案,在开发过程中给予开发人员以引导,有助于开发人员方便、快捷、正确地选用组件,同时也便于系统的维护。 组件管理可借助于组件管理工具软件——组件管理器进行管理。组件管理器可以对组件进行文本化和可视化管理。

93 2) 组件管理的内容 根据组件自身的特点和组件管理的目的,组件管理的内容至少应该包括: ① 可视化组件的种类、名称、参数定义。 ② 对组件的图形化描述。 ③ 组件的分类、文本化和目录化。 ④ 组件的转换和变更。

94 应用组件技术开发应用系统 1. 推荐模型开发 推荐模型开发的目标是应用先进的信息技术,融合先进的管理理论和众多成功企业的最佳业务实践,向用户推荐满足用户需求的开发模型,以便用户参考。推荐模型仍以组件为基础,可以看作是组件的有机组合。为了开发出能够满足用户需求的推荐模型,在开发推荐模型过程中可以对组件不断地进行修改。

95 一个优秀的推荐模型应该包括:组织模型、过程模型、数据模型和功能模型。
(1) 组织模型——提供了根据不同用户功能和任务抽象出的组织结构模型。 (2) 过程模型——根据离散型、流程型、混合型和服务型用户的不同特点所建立的业务过程处理模型。 (3) 数据模型——建立整个业务处理过程中全部数据的处理模型。 (4) 功能模型——为系统内不同用户提供了实际业务过程处理的操作界面。

96 2. 应用组件技术开发应用系统 1) 用组件技术开发应用系统的开发步骤 (1) 基本组件开发——开发人员应用开发工具开发各种不同功能的基本组件,并将所开发的组件存储到组件库中,以便实施统一的管理。 (2) 推荐模型的开发——结合先进管理理论和一些成功的开发实例,应用组件构造待开发系统的推荐模型。 (3) 应用系统的开发——根据用户的不同特点,在系统推荐模型的基础上,进行一系列的修改、调整,最终形成应用系统。

97 2) 应用组件技术开发应用系统模型 图11.2 应用组件技术开发应用系统模型

98 组件技术无疑给应用系统的开发者带来了方便,但组件技术的发展还很不成熟,还没有一个统一的规范,这给开发又带来不便。因此,在实施软件开发的过程中,应综合考虑各个方面的因素来选择软件开发方法。


Download ppt "第11章 面向对象实现 11.1 面向对象的程序设计语言 11.2 面向对象的程序实现特征 11.3 面向对象测试 11.4 组件技术简介."

Similar presentations


Ads by Google