在学习面向对象程序设计之前,一般都会学习面向过程的程序设计,例如,使用C面向过程的程序设计语言,面向过程的语言是按流程化的思想来组织的。在这些语言的设计思想中,通常将存放基本数据类型的变量作为程序处理对象、以变量的赋值作为程序的基本操作、以变量值的改变作为程序运行的状态。这种程序设计风格存在着数据抽象简单、信息完全暴露、算法复杂、无法很好地描述客观世界等缺点。在程序设计过程中,为了实现有限度的代码重用,公共代码被组织成为过程或函数。当需要代码重用时,调用已经组织好的过程或函数。在这种应用方式中,如果软件项目庞大,程序的调试和维护将变得异常困难。
而面向对象的程序设计思想是将数据以及对于这些数据的操作,封装在了一个单独的数据结构中。这种模式更近似于现实世界,在这里,所有的对象都同时拥有属性以及与这些属性相关的行为。对象之间的联系是通过消息来实现的,消息是请求对象执行某一处理或回答某些信息的要求。某个对象在执行相应的处理时,可以通过传递消息请求其他对象完成某些处理工作或回答某些消息。其他对象在执行所要求的处理活动时,同样可以通过传递消息和另外的对象联系。所以,一个面向对象程序的执行,就是靠对象间传递消息来完成的。
面向对象程序设计是一种新兴的程序设计方法,或者是一种新的程序设计规范,它使用对象、类、继承、封装、消息等基本概念来进行程序的设计。从现实世界中客观存在的事物(即对象)出发来构造软件系统,并且在系统构造中尽可能运用人类的自然思维方式。开发一个软件是为了解决某些问题,这些问题所涉及的业务范围称作该软件的问题域。其应用领域不仅仅是软件,还有计算机体系结构和人工智能等。
使用UML进行系统建模时,首先就必须弄清楚什么是对象,在系统的分析与设计过程中如何利用对象。
从事软件开发的工程师们经常会有这样的体会:在软件开发过程中,客户会不断地提出各种修改要求,即使在软件投入使用后,也常常需要对其做出修改,在用结构化开发的程序中,这种修改往往是很困难的,而且还会因为计划或考虑不周,不但旧错误没有得到彻底改正,又引入了新的错误;另一方面,在过去的程序开发中,代码的重用率很低,使得程序员的效率并不高,为提高软件系统的稳定性、可修改性和可重用性,人们在实践中逐渐创造出软件工程的一种新途径——面向对象方法。
面向对象方法的出发点和基本原则是尽可能模拟人类习惯的思考问题的方式,使软件开发的方法与过程尽可能接近人类认识世界、解决问题的方法与过程。由于客观世界的问题都是由客观世界中的实体及实体相互间的关系构成的,因此我们把客观世界中的实体抽象为对象。也就是说“面向对象 ” 是一种认识客观世界的世界观,是从结构组织角度模拟客观世界的一种方法。
根据上述可知,面向对象所带来的好处是程序的稳定性与可修改性(由于把客观世界分解成一个一个的对象,并且把数据和操作都封装在对象的内部)、可重用性(通过面向对象技术,不仅可以重用代码,而且可以重用需求分析、设计、用户界面等)。
面向对象方法具有以下几个要点:
(1)客观世界是由各种对象组成的,任何事物都是对象,复杂的对象可以由比较简单的对象以某种方式组合而成。按照这种观点,可以认为整个世界就是一个最复杂的对象。因此,面向对象的软件系统是由对象组成的,软件中的任何元素都是对象,复杂的软件对象由比较简单的对象组合而成。
(2)把所有对象都划分成各种对象类,每个对象类都定义了一组数据和一组方法,数据用于表示对象的静态属性,是对象的状态信息。因此,每当建立该对象类的一个新实例时,就按照类中对数据的定义为这个新对象生成一组专用的数据,以便描述该对象独特的属性值。
例如,在屏幕上不同位置显示的半径不同的几个圆,虽然都是Circle类的对象,但是,各自都有自己专用的数据,以便记录各自的圆心位置、半径等。
类中定义的方法,是允许施加于该类对象上的操作,是该类所有对象共享的,并不需要为每个对象都复制操作的代码。
(3)按照子类与父类的关系,把若干个对象类组成一个层次结构的系统。
(4)对象彼此之间仅能通过传递消息进行联系。
对象(Object)是面向对象的基本构造单元,是系统中用来描述客观事物的一个实体,一个对象由一组属性和对属性进行操作的一组方法组成。
对象不仅能表示具体的实体,也能表示抽象的规则、计划或事件。主要有如下的对象类型:
(1)有形的实体:指 一 切看得见、摸得着的实物。如汽车、书、计算机、桌子、鼠标、机器人等等,都属于有形的实体,也是最易于识别的对象。
(2)作用:指人或组织,如医生、教师、员工、学生、公司、部门等所起的作用。
(3)事件:在特定时间所发生的事,如飞行、事故、中断、开会等。
(4)性能说明:制造厂或企业,往往对产品的性能进行全面的说明,如车厂对车辆的性能说明,往往要列出型号及各种性能指标等。
对象不仅能表示结构化的数据,而且也能表示抽象的事件、规则以及复杂的工程实体,这是结构化方法所不能做到的。因此,对象具有很强的表达能力和描述功能。
总之,在面向对象的系统中,对象是一个封装数据属性和操作行为的实体。数据描述了对象的状态,操作指的是操作私有数据,改变对象的状态。当其他对象向本对象发出消息,本对象响应时,其操作才得以实现,在对象内的操作通常叫做方法。
对象具有如下特征:
(1)模块性
模块性指的是对象是一个独立存在的实体。从外部可以了解它的功能,其内部细节是 “ 隐蔽 ” 的,不受外界干扰,对象之间的相互依赖性很小。因此,模块性体现了抽象和信息的隐蔽。它使得一个复杂的软件系统可以通过定义一组相对独立的模块来完成,这些独立模块之间只需交换 一 些为了完成系统功能所必须交换的信息就行。当模块内部的实现发生变化而导致必须要修改程序时,只要对外接口操作的功能不变,就不会给软件系统带来影响。
(2)继承
继承是利用已有的定义作为基础来建立新的定义,而不必重复定义它们。
例如,汽车具有“车型”、 “ 颜色”和 “ 出厂日期”等属性,其子类吉普车、轿车及卡车都继承了这些属性。
(3)动态连接性
各个对象之间是通过传递消息来建立连接的。消息传递机制是面向对象语言的共同特性,其含义是将 一 条发送给一个对象的消息与包含该消息方法的对象联接起来,使得增加新的数据类型不需要改变现有的代码。
一个类定义了一组大体上相似的对象。一个类所包含的方法和数据描述一组对象的共同行为和属性。例如,窗口、车轮、玻璃等都是类的例子。类是在对象之上的抽象,有了类以后,对象则是类的具体化,是类的实例。类可以有子类和父类,形成层次结构。
类是对事物的抽象,它不是个体对象,而是描述一些对象的完整集合。
例如,你可以把CPU看作是对象类,它具有CPU的共同属性,如:主频、指令集、Cache容量、运算位数、功率等。也可以考虑CPU的某个具体实例,如 “ Intel的P4处理器”。
把一组对象的共同特性加以抽象并储存在一个类中,是面向对象技术最重要的一点;是否建立了一个丰富的类库,是衡量一个面向对象程序设计语言成熟与否的重要标志。
类是静态的,类的语义和类之间的关系在程序执行前就已经定义好了,而对象是动态的,对象是在程序执行时被创建和删除的。
如图2.1所示的是类的例子,其中类的名字是图书Book。
该类有9个属性(b_id,b_name,t_id,p_id,author,isbn,r_date,price,quantity),7个方法(add,delete,update,querybyname,querybypid,querybyauthor,queryall)。
图2.1 图书类
实例这个概念和对象很相似,在UML中,会经常使用实例这个术语。一般说来,实例这个概念比较广泛,它不仅仅是对类而言,其他建模元素也有实例。例如,类的实例是对象,而关联的实例就是链。
一 般的,实例就是由某个特定的类所描述的一个具体的对象。类是对具有相同属性和行为的一组相似的对象的抽象,类在现实世界中并不能真正存在。在地球上并没有抽象的 “ 中国人 ” ,只有一个个具体的中国人,例如,张名、李洋等等,同理,也没有抽象的 “ 圆 ” 。
实际上类是建立对象时使用的 “ 模板 ” ,按照这个模板所建立的一个个具体的对象,就是类的实际例子,通常称为实例。
封装(Encapsulation)就是把一个对象的方法和属性组合成一个独立的单位,并尽可能隐蔽对象的属性、方法和实现细节的过程,仅仅将接口进行对外公开。例如,图2.1中的图书类就反应了类的封装性,在图书类中,将属性(如:b_id,b_name等)和方法(如:add(),delete()等)进行了组合,对外只提供了图书类的一些属性和方法,而对于方法的具体实现过程是隐藏的。
在访问类的时候,根据其封装的特点,对外访问时提供了4种访问控制级别:
(1)public:公有访问。最高一级的访问,所有的类都可以访问。
(2)protected:受保护的。只有同一个包中的类或者子类可以进行公开访问。
(3)private:私有访问。最低一级的访问,只能在对象的内部访问,不对外公开。
(4)default:默认的。属于当前目录(包)下的类都可以访问。
因此,根据类的封装性,在对属性和方法进行访问时,就需要知道其访问的控制级别,否则是不能使用的。实质上,封装包含了两层含义: 一 层是把对象的全部属性和方法结合在一起,形成一个不可分割的独立单位。对象的属性(除了公有访问的属性)只能由这个对象的方法来存取;二是极大可能地隐蔽了对象的内部实现细节,与外部的联系只能通过外部接口来实现。
封装将类的信息进行了隐蔽,使得类彼此相对独立,对于一个类可以只考虑其对外所提供的接口,即有什么功能,能做什么,而不需要注意其内部实现的细节,也就是说这些功能是如何实现的。例如,在图2.1中的图书类中,其提供了图书信息的增加、删除、修改以及查询等功能,也就是说,使用图书类能做的事情就是对图书信息的基本维护,而对其查询、增加、删除和修改等功能的内部实现是隐藏的。
将类进行了封装,使得对象以外的部分不能随意对对象的内部属性和方法做修改,从而有效地避免了外界产生的错误对其造成的影响,极大地降低了查错和排错的难度。此外,当对对象的内部实现做修改时,由于其只是通过一些外部的接口对外提供服务,同样也减小其内部的修改对外界所造成的影响。
封装机制将对象的开发者和使用者进行分离,使用者不需要知道对象具体的实现细节,只需要使用开发者提供的外部接口,就可以使用对象提供的功能。因此封装的结果实际上将对象的复杂实现进行了隐蔽,并提供了代码的可重用性,从而降低了软件开发的难度。
综上所述,封装的最大优点是:
(1)方便了使用者对类和对象的操作,并降低了使用者错误修改其属性的可能性。
(2)体现了系统之间的松散耦合关系并提高了系统的独立性。
(3)提高了程序的复用性。
(4)针对大型的开发系统,降低了开发风险。如果整个系统开发失败。一些相对独立的子系统仍然存在可用价值。
综上所述,系统的封装性越高,相对独立性就越强,并且使用也比较方便。
客观世界的事物除了具有共性外,还存在着特性。如果只 一 味地考虑事物的共性,而忽略了事物的特性,就不能反映出客观世界中事物之间所存在的层次关系,也就不能正确、完整、详细地描述客观世界。如果在分析客观世界的事物时,先忽略其特性,抽取事物的共性,这样就能够得到一个适合客观事物某个集合的类。如果在这个抽象类的基础上,再考虑在对事物进行抽象的过程中每个对象被忽略的那部分特性,增加特性后就能够形成一个新的类,这个新类具有前一个类的全部特征,是前一个类的子集,这两个类之间就形成了一种层次结构,称之为继承结构。
继承(Inheritance)是一种一般类与特殊类的层次模型。继承性是指特殊类的对象具有其 一 般类的属性和方法,在其之上又增加了自己的特殊属性和方法。继承意味着在特殊类中不用重新定义在一般类中已经定义过的属性和方法,特殊类可以自动地、隐含地拥有其一般类的属性与方法。继承体现了类之间代码的重用性特点,提供了一种明确表达共性的方法。对于一个特殊类,既有自己新定义的属性和方法,还有从一般类中继承下来的属性和行为。尽管继承下来的属性和行为是隐藏的,但无论在概念上还是在实际使用效果上,都是这个类的属性和行为。当这个特殊类又被它更下层的特殊类继承时,它继承来的和自己定义的属性和方法又被下一层的特殊类继承下去。因此,继承具有传递性,体现了客观世界中特殊与一般之间的关系。
在继承中,需要明确这样两个概念,子类和父类。
子类:指的是通过继承创建的新类称为“子类” 或者“派生类”。
父类:指的是被继承的类称为“基类”、“ 父类” 或“ 超类”。
继承的过程,就是从一般到特殊的过程。继承性提供了父类和子类之间共享数据和方法的一种机制。继承表示的是类之间的一种关系,在定义和实现一个类的时候,可以通过一个已经存在的类来创建新类,,把这个已经存在的类作为父类,将其所定义的内容作为自己的内容的 一 部分、并加入一些新的内容。如图2.2表示了父类A和它的子类B之间的继承关系,箭头从子类B指向父类A。子类B由继承部分(C)和增加部分(D)组成。
图2.2 继承
继承性分为单重继承和多重继承两类。
单重继承:指的是一个子类只有一个父类;
多重继承:指的是一个子类可以有多个父类。单重继承和多重继承时父类和子类之间的关系如图2.3所示,其中(a)表示的是单重继承,(b)表示的是多重继承。
图2.3 继承性
单重继承所表示的类之间的关系类似 一 棵树,如图2.3中的(a)。在图中,每个类都只有一个父类,如类A是最顶层的父类,类B、C和D是类A的子类,类C是类E和F的父类。多重继承所表示的类之间的关系比单重继承复杂,一个类可以有多个父类对应,如图2.3(b)中的类E和F,其中类E的父类是类B和D,而类F的父类是类C和D。
此外,继承关系是可传递的,如图2.3(a)中的类F继承类C,而类C继承类A,因此类F也继承了类A,所以类F也是类A的子类,是间接的子类, 类C则是类A的直接子类。
在软件开发过程中,继承性体现的是软件模块的可重用性和独立性,可以缩短软件的开发周期,提高软件的开发效率,并为日后的维护和修改软件提供了便宜。因为如果要修改某个模块的功能,只需在相应的类中进行一些变动,而它派生的所有类都自动地、隐含地作了相应的改动。
综上所述,继承真实地反映了客观世界中事物的层次关系,通过类的继承,能够实现对问题的深入抽象描述,反映出事物的发展过程,继承性是面向对象程序设计语言不同于其他语言的最主要的特点。
客观世界的事物具有特性,可以以不同的形态存在,在面向对象程序设计中也参考了客观世界的多态性特点。
也就是说类具有多态性,它体现了在不同的对象收到相同的消息后,可以产生多种不同的行为方式。多态性使得同一个属性或行为在父类及其各派生类中可以具有不同的语义。例如,在一般类“几何图形”中定义了 “ 计算图形面积、周长或绘制图形”等行为,但是这些行为并不具备具体的含义,也就是说还不确定要计算什么几何图形的面积或者是绘制一个什么样的图形。根据一般类再定义特殊类如 “ 矩形” 、 “ 正方形”、 “ 圆 ” 和 “ 梯形”等,它们都继承了父类“几何图形”的 “ 计算图形面积、周长或绘制图形”等行为,因此自动具有了 “ 计算面积或绘制图形”的功能,但每个特殊类的功能却不一样,一个是要计算矩形的面积或画出一个矩形,另一个是计算正方形的面积或是要画出一个正方形等功能。这样的计算面积或绘图的消息发出后,矩形、正方形等类的对象接收到这个消息后各自执行不同的计算面积或绘图函数。如图2.4所示的就是多态性的表现。
图2.4 多态
具体来说,多态性(Polymorphism)是指类中同一函数名对应多个功能相似的不同函数,可以使用相同的调用方式来调用这些具有不同功能的同名函数,这些同名的函数可以是参数的个数或是类型不同,但是函数名相同,当进行调用的时候,根据所传的数据选定相应的函数,从而去执行不同的功能。
在面向对象程序设计中通过继承性和多态性的结合,可以生成许多类似但是功能却各不相同的对象。根据继承性的特点,这些对象共享 一 些相似的特征,并显出自己的特性;根据多态性,针对相同的消息,不同对象可以具有特殊的表现形式,实现个性化的设计。
在面向对象程序设计中,对象之间要进行数据的传递,那么对象之间靠什么来传递数据?对象之间是通过消息进行通信的,多个对象之间通过传递消息来请求或提供服务,从而使一个软件具有更强大的功能。
在面向对象的系统中,把 “ 请求”或“命令”抽象成“消息”,当系统中的其他对象请求这个对象执行某个服务时,就将一个消息发送给另一个对象,接收到消息的对象将消息进行解释,然后响应这个请求,完成指定的服务。通常,把发送消息的对象称为发送者,把接收消息的对象称为接收者。通常,一个消息由以下几部分组成:
提供服务的对象名。
服务的标识,即方法名。
输入信息,即实际参数。
响应结果,即返回值或操作结果。
消息是实现对象之间进行通信的一种机制,对于一个对象可以接收不同形式的多个消息,并产生不同的结果;相同形式的消息可以发送给不同的对象,并产生不同的结果;在发送消息的时候可以不考虑具体的接收者,对象可以对消息做出响应,也可以拒绝消息,也就是说对消息做出响应不是必须的。