1.4 面向对象方法学
现代软件工程主要指的是面向对象的软件工程。所谓面向对象,就是针对现实中客观存在的事物进行软件开发。这是类似于人的直观思维方式的。
众所周知,客观世界是由许多不同的具有自己的运动规律和内部状态的对象构成。不同的对象之间相互作用和交互形成了完整的客观世界。因此,从思维模式的角度,面向对象与客观世界相对应,对象概念就是现实世界中对象的模型化。从人类认知过程的角度来看,面向对象的方法既提供了从一般到特殊的演绎手段(如继承),又提供了从特殊到一般的归纳形式(如类目)。面向对象方法学是遵循一般认知方法学的基本概念而建立起来的完整理论和方法体系。因此,面向对象方法学也是一种认知方法学。
图1-3 从现实问题到对象解空间映射
从软件技术角度来讲,面向对象方法起源于信息隐蔽和抽象数据类型概念,它以对象作为基本单位,把系统中所有资源,如数据、模块以及系统都看成对象,每个对象把一组数据和一组过程封装在一起。面向对象方法是面向对象技术在软件工程的全面应用。如图1-3所示,面向对象方法从现实世界中的问题域直接抽象,确定对象,根据对象的特性抽象,用类来描述相同属性的对象,而类又分成不同的抽象层次,类成为面向对象设计的最基本模块,它封装了描述该类的数据和操作,数据描述了对象具体的状态,而操作确定了对象的行为。
1.4.1 面向对象方法学的起源
面向对象方法学的发展历史大致可以划分为四个阶段。
1)萌芽阶段(20世纪50年代)
在20世纪50年代初,面向对象方法中的“对象”“属性”等概念第一次出现在关于人工智能的著作中。到50年代后期,随着面向对象的编程语言(object-oriented programming language,OOPL)的出现,面向对象的思想开始真正的蓬勃发展。为了避免变量名在不同部分发生冲突,ALGOL语言的设计者在ALGOL60中采用了以“Begin...End”为标识的程序块,使得块内变量名是局部的,从而避免它们与程序中块外的同名变量相冲突。这是编程语言中首次提供封装的尝试。此后程序块结构广泛用于高级语言如Pascal、Ada、C之中。
2)初期阶段(20世纪60年代)
20世纪60年代中期,由挪威计算中心和奥斯陆大学共同研制的Simula语言,在ALGOL基础上,首次引入了类、继承和对象等概念,成为面向对象方法学在软件工程领域的起源标志。在Simula67影响下,70年代Xerox PARC研究所发明了以类为核心概念的Smalltalk编程语言。在Smalltalk中,对象和消息广泛地应用在了基础的运算中,而且相比Simula67, Smalltalk中的对象是动态的,而并非Simula中的静态对象。在Smalltalk之后,在1980年, Xerox研究中心又推出Smalltalk-80系统,其强调了对象概念的统一,并引入了方法、实例等概念和术语,应用了单重继承机制和动态链接。它从界面、环境、工具、语言以及软件可重用等方面对软件开发工作提供了较为全面的支持,使得面向对象程序设计趋于完善,掀起了面向对象研究的高潮。
3)发展阶段(20世纪80年代中期到90年代)
该阶段,受到Smalltalk-80的影响,大批面向对象编程语言相继涌出,如Object-C、Eiffel、C++、Java、Object-Pascal等。
20世纪80年代中期,C语言扩展到面向对象的领域上,于是C++在80年代应运而生。C++保留了C语言的原有特性,同时增加了面向对象的支持。因此,C++是一种既支持面向过程编程,又支持面向对象编程的混合式编程语言。
在C++之后,Java和C#是最为广泛应用的面向对象编程语言。它们都引入了虚拟机的概念,且语法上都与C和C++相近。这两种语言是更为纯粹的面向对象语言。近些年来动态语言如Python、Ruby的流行,又推动了面向对象技术的发展。
1989年,Object Management Group(OMG)公司建立。OMG的使命是建立工业标准,细化对象管理描述和应用开发的通用框架。统一建模语言(unified modeling language,UML)就是由OMG维护的众所周知的描述之一。UML是为软件系统的制品进行描述、可视化、构造、归档化的一种语言。它同样适用于商业模块和其他非软件系统。
4)成熟阶段(20世纪90年代之后)
自1990年,面向对象分析(object oriented analysis,OOA)和面向对象设计(object oriented design,OOD)被广泛研究,许多专家都在尝试不同的方法进行面向对象分析和设计。其中比较著名的方法有Grady Booch方法、Jocobson的OOSE方法、Rumbaugh的OMT方法等,这些方法各有所长。这段时期,面向对象分析和设计技术逐渐走向实用,最终形成了从分析、设计、编程、测试到维护的一整套软件开发体系。其中在支持面向对象建模的方法学的竞技中,统一建模语言UML最终成为建模领域的标准。
1.4.2 面向对象方法学的核心概念
面向对象方法学可以用下式表述:
面向对象方法学=对象+类+继承+基于消息的通信
即面向对象使用了对象、类和继承的机制,同时对象之间只能通过传递消息来实现相互通信。
1)对象(object):一切都是对象
自然界存在的一切事物都可以称作对象。例如学生是对象,老师是对象,教室是对象,一个学校也是一个对象。对象是其自身所具有的状态特征和作用于这些状态特征的操作集合一起构成的独立实体。对象包含两个要素:描述对象静态特征的属性和描述对象动态特征的操作。对象是面向对象方法学的基本单位,是构成和支持整个面向对象方法学的基石。
2)类(class):物以类聚
类是对具有相同属性、特征和服务的一个或一组对象的抽象定义。类与对象是抽象描述与具体实例的关系,一个具体的对象称作类的一个实例(instance)。例如学生是对所有种类的学生的抽象,某个学生小张可以看作是学生类型的一个实例。
3)继承(inheritance):世界的相似性与多样性
世界万物既有相似性,又有多样性。通过继承机制,可以达到相似性与多样性的统一。一方面子类继承父类定义的属性和操作,另一方面,子类又可以添加自己的属性和操作,或者通过多态机制使得父类中定义的操作有自己的实现。
4)基于消息的通信(communication with message):消息,合作之道
消息(message)是面向对象软件中对象之间交互的途径,是对象之间建立的一种通信机制。通常是指向其他对象发出服务请求或者参与处理其他对象发来的请求。一条消息的必备信息有:消息名、消息请求者、消息响应者、消息所要求的具体服务和参数等。
消息通信(communication with messages)也是面向对象方法学中的一条重要原则,它与对象的封装原则密不可分。封装使对象成为一些各司其职、互不干扰的独立单位;消息通信则为它们提供了唯一合法的动态联系途径,使它们的行为能够互相配合,构成一个有机的系统。
1.4.3 面向对象的特性
1)抽象(abstraction)
抽象是指强调实体的本质、内在的属性和行为,而忽略一些无关的属性和行为。抽象描述了一个对象的内涵,可以将对象与所有其他类型的对象区分开来。对于给定的问题域决定一组正确的抽象是面向对象设计的核心问题。
2)封装(encapsulation)
封装是指把对象的属性和操作结合成一个独立的系统单位,并尽可能的隐藏对象的内部细节。封装是对象和类的一个基本特性,又称信息隐藏。通过对象的封装性,用户只能看到对象封装界面上的信息,对象内部对用户是透明的,从而有效地实现了模块化功能。封装可以使对象形成接口和实现两个部分,将功能和实现分离,避免错误操作。
3)多态(polymorphism)
多态指一般类中定义的属性或方法被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。使用多态技术时,用户可以发送一个通用的消息,而实现的细节则由接受对象自行决定,这样同一消息就可以调用不同的方法。多态性不仅增加了面向对象软件系统的灵活性,进一步减少了信息冗余,而且显著提高了软件的可重用性和可扩充性。
1.4.4 类之间的关系
类之间存在三种基本的类关系:①一般特殊关系,表示“是一种”关系(is a),例如,樱花是一种花,花是一般的类,而樱花是一种特殊的子类;②整体部分关系,表示“组成部分”关系(has a/contains a),如花瓣是花的一部分;③关联关系,表示某种语义上的依赖关系(use a),相互关联的两个对象一般是平等的,例如,蜜蜂和花之间的关系。
1)一般特殊关系
(1)继承(inheritance)。继承是面向对象方法学中的一个十分重要的概念,继承是指能够直接获得已有的性质或特征,不必重新定义。在面向对象的方法学中,其定义是:特殊类(或称子类、派生类)的对象拥有其一般类(或称父类、基类)的全部属性与服务,称作特殊类对一般类的继承。比如樱花是子类,花是基类。继承可以表示类与类、接口与接口之间的继承关系,或类与接口之间的实现关系。继承分为单继承和多继承。当一个类只有一个父类时为单继承,有多个父类时为多继承。
(2)泛化(generalization)。泛化与继承相反,是指从子类抽取共同的特征形成父类的过程。例如,从关山樱、菊樱、郁金樱等不同种类樱花中,抽取樱花类。
2)整体部分关系
(1)聚合(aggregation)。聚合表示整体类和部分类之间的关系为“包含”“组成”的关系。例如,花包含了樱花、桃花、梅花等,当这些花不构成完整的花类时,也是单独存在的。
(2)组合(composition)。组合表示整体类拥有部分类,部分和整体具有相同的生存期,如果整体不存在了,部分也随之消失。例如一朵花包含花冠、花萼、花托和花蕊四个部分,花不存在了,则花冠等也没有意义。组合是一种特殊形式的强类型的聚合。
3)关联关系
(1)关联(association)。关联是体现两个类之间语义级别的一种强依赖关系,一般是长期的且双方是平等的。关联可以是单向的和双向的。
(2)依赖(dependency)。依赖是类与类之间的连接,表示一个类依赖于另一个类的定义,其中一个类的变化将影响另外一个类,依赖关系具有偶然性、临时性,是脆弱的。
(3)实现(realization)。指的是一个类实现接口(可以是多个)的功能;实现是类与接口之间最常见的关系。
1.4.5 面向对象的优点
面向对象技术提供了更好的抽象能力和更多的软件开发方法和工具,能够使用各种不同的设计模式来解决具体问题。而且,在软件实现层面上看,面向对象技术极大地提高了代码复用,提高了代码的可扩展性,便于软件的维护。
面向对象技术中,对象是整个技术的核心。而整个软件系统,是真实世界的一种抽象。这种抽象,是由描述状态的数据,以及描述动作的方法的整体封装。不同的对象之间可以相互传递消息,类似于现实世界中不同事物之间的交流和联系。因为面向对象理念中,建立起来的模型是对真实世界的反映,所以开发者可以更多地站在真实世界——软件应用领域——的角度去看待问题,而不需要把应用领域的问题转化为计算机的角度来考虑。这样的思考方法无疑更加接近于人的传统思维方式,对于问题的考虑也将更加完善。
传统的软件开发方法是“瀑布”模型的,强调自顶向下完成软件开发过程。然而事实上,人们对于问题的认识是一个渐进的过程。通过不断深化对问题的理解,人们的思维经历了从特殊到一般的归纳,也经历了从一般到特殊的演绎,这都是在第一次分析问题时所难以达到的。人们在认识复杂的问题时,运用最多的方式是抽象,即忽略不关注的方面,而重点分析处理关注的方面,这与面向对象的思想是一致的。
通过面向对象技术建立起来的模型,可以随着开发者对于问题理解的深入而进行完善和修改。由于类与类之间是相对独立的,因此不会出现牵一发而动全身的情况。当系统的功能需求改变时,软件的结构不会出现大的变化,一般情况下只需要进行简单的修改和调整。因为对象是对真实世界的反映,而真实世界的结构是相对稳定的,因而以面向对象技术构建出的系统结构也是比较稳定的。
在传统的工业界,用已有的零件来装配新的产品是非常普遍的情况。实际上,新的产品并不是全新的,仅仅是部分零件做了更新而已。软件开发同样是如此,一个新的软件并不需要完全重写所有的代码。这种时候,代码复用就可以很大程度地提高生产效率。在传统的软件工程中,代码的复用是利用标准函数库实现的。但是标准函数库很难适应不同的应用场合和不同的需求,因而这种复用是很基本的。函数库仅仅能提供最为基础的功能,在一个软件系统中,绝大多数函数都需由开发者重新编写。然而,面向对象的开发方法在构建软件系统时,可以通过派生已有的类来实现代码的复用。子类不仅继承了父类的数据和方法,还可以很方便地进行扩充和修改。可以这么说,在面向对象软件开发中,对象是一个个的细胞,有自己独立的结构、功能和用途。开发大型软件的过程就是对小“细胞”进行组合的过程。这样就把大型的软件系统拆分成了相对独立的小“模块”,从而大大降低了开发的难度和管理的复杂度。
基于面向对象技术开发的软件由于稳定性比较好,当出现需求变更时软件也比较容易修改,因而软件的维护难度也大大降低了。
传统的软件难以维护,另一个重要的原因是整个系统难以理解。尤其是对于比较庞大的系统,需要修改的部分经常比较分散,而人们又很难了解整个软件的全部内容。面向对象符合人类惯有的思维方式,在这种方式下建立起来的软件架构与真实世界基本相同,因而减小了理解的难度,也降低了维护的难度。
由于面向对象开发的软件各个类之间的独立性比较好,在更改时,往往只需要修改类的局部数据或操作,所以比较容易实现。继承和多态机制能够使得对软件修改和扩充时,需要修改或增加的代码大量减少。除此之外,为了保证软件的质量,大量的测试是必需的。基于面向对象技术的软件中,由于类是独立性很强的小模块,因此要完成对类的测试是简单的——创造类对象,进行各种功能的测试即可,调试难度也比较低,因此可维护性较强。