1.2 面向对象程序语言的特征
概括而言,程序的组织方式大致有两种:以功能为中心或以数据为中心。面向过程程序设计以功能为中心组织程序,而面向对象程序设计(Object-Oriented Programming,OOP)则围绕数据进行程序组织,以数据为中心,围绕数据设计程序代码,由用户定义数据及可实施于数据的操作。面向对象设计语言具有抽象、封装、继承和多态等基本特征。
面向对象程序设计语言经历了一个较长的演变过程,20世纪50年代的LISP语言就引入了信息隐藏和封装机制,60年代的Simula语言,提出了抽象和封装,引入了数据抽象和类的概念,被认为是第一个面向对象语言(但它不具备面向对象语言的全部特征);70年代的Smalltalk则是第一个真正面向对象的程序设计语言。现在广泛使用的C++、Eiffel、Object-C、Visual Basic、PowerBuilder、Delphi、C#、Java等都是面向对象的程序设计语言。
1.类与对象
在现实生活中,对象是指客观存在的事物,如一个人、一条狗、一棵树、一台计算机等都是对象。同类对象具有相同的属性(特征)和行为,比如所有的人都有姓名、性别、眼、手脚、身高、体重等属性,有走路、讲话、打手势、学习和工作等行为;楼房有位置、楼层、房间数量、造价等属性;狗有皮毛、尾巴、四条腿等属性,有跑、吠、摇尾巴等行为;飞机能够飞行,轮船能够航行;如此等等。
人们借助于对象的属性和行为认识客观世界,将具有相同属性和行为的客观对象归入同一类。利用这种分类方法,人们将客观世界分成了人类、狗类、猪类、树类、草类等。
每类事物都有许多实际存在的个体,这些个体则称为对象(Object)。同类事物的不同个体,尽管有着相同的一组属性和行为,但在属性取值和行为表现上存在个体差异。比如,张三和李四都具有人类所共有的属性和行为,但他们在走路、讲话、打手势、学习和工作等行为的实施方面有各自的特点,这是行为上的个体差别;他们具有不同的名字(姓名属性不同),张三1.7米高,李四1.5米高(身高属性不同),这些是属性取值的差异。在现实中,人们借助于对象的属性和行为认识和区分不同事物。
面向对象程序设计用计算机中的软件对象模拟现实中的实际对象。它用类(class)来表示同类对象的共有属性和行为,即用类这一概念来表示客观世界中的同类事物。类是用归类方法从一个个具体对象中抽取出共同特征而形成的概念。比如,张三、李四、王二……都是学生,都有学号、姓名、班级、性别等属性,具有做作业、听课等行为。将所有同学都共有的这些属性和行为抽象出来,就构成了学生类。而具有一个类所指定的属性和行为的某个个体则称为该类的一个对象。图1-3表示了一个学生类和其中一个对象的关系。
图1-3 类与对象的关系
2.抽象与封装
抽象(abstract)是指有意忽略问题的某些细节和与当前目标无关的方面,以便把问题的本质表达得更清楚。抽象在现实生活中随处可见,如画一幅中国地图,如果不分主次把所有的山川、河流、城市、交通线路全画上去,最后的结果将是一团糟。只能通过抽象,画出中国各大省份的概况,如主要大城市、主要山脉、重要的交通线路、主要的大江大河等,有意不画出各省中不重要的县及小城镇,不画出各城市中的各条街道、公路等。道理很简单,这样更能反映出中国地理的整体面貌,让人们更加清楚地了解中国地理的分布情况。
面向对象程序设计通过数据抽象来描绘客观事物,把事物的主要特征抽取出来,有意地隐藏事物某些方面的细节,把注意力集中在事物的本质特征上面,更能把握问题的本质。数据抽象的结果将产生对应的抽象数据类型(Abstract Data Type,ADT)。
ADT把数据类型分成了接口和实现两部分。其中,对用户可见、用户能够用来完成某项任务的部分称为接口;那些对用户不可见、具体完成工作任务的细节则称为实现。这如同人们常见的DVD播放机,面板上可供用户操作使用的各种功能按钮就是DVD的接口,各个功能按钮(如Play)完成相应功能的细节就是实现(例如,Play如何播放DVD的具体过程,即怎样旋转光盘,移动激光柱,读取光盘内容,并在电视机屏幕上显示光盘中的图像等过程则称为Play接口的实现)。实现对用户是隐藏的,用户不知道实现的细节,也不需要知道。
数据抽象的任务是设计出清晰而足够的接口,接口必须满足用户的基本需求,且允许用户通过它访问其底层实现。抽象的结果导致了接口与实现的分离,具体可以通过数据封装实现。所谓封装(encapsulation),就是将数据抽象的外部接口与内部实现细节分离开来,将接口显示给用户并允许其访问,将接口的实现细节隐藏起来,不让用户知道,也不允许他访问。
在面向对象程序中,封装是通过类来实现的。类是表示客观事物的抽象数据结构,它将数据及其操作函数包装成一个整体,且将内部的实现细节隐藏起来,不让类外其他对象知道,但提供了可让外界访问其功能的接口,通过接口与外界联系。
例如,前面的个人通信录管理程序,在面向对象程序设计语言(如C++)中的简易模型如下,其中的class是用于将数据和函数组合成一个整体(即类)的语言机制。
class Person{ //用于存取个人信息的数据结构 private: char name[10]; char addr[20]; char phone[11]; public: void initData(){……} //读入姓名、地址和电话 char* getAddr(){……}; //返回地址 char* getPhone(){……}; //返回电话号码 };
在这个程序模型中,Person的name、addr、phone和操作数据的函数initData()、getAddr()、getPhone()被class捆绑在一起,它们是一个整体,这就是封装。其中,private区域中的数据定义对外部程序是隐藏的,public区域中的函数是提供给外部函数访问Person类功能的接口。程序中的任何函数只有通过public区域中的initData()、getAddr()和getPhone()等函数才能修改或查看Person的name、addr和phone数据。除此之外,再无他法。
3.继承
继承源于生物界,通过继承,后代能够获得与其祖先相同或相似的特征与能力。面向对象程序设计语言也提供了类似于生物继承的语言机制,允许一个新类从现有类派生而来,新类能够继承现有类的属性和行为,并且能够修改或增加新的属性和行为,成为一个功能更强大、更能满足应用需求的类。
继承是面向对象程序设计语言的一个重要特征,是实现软件复用的一个重要手段。在面向对象程序设计中,如果一个类B继承了另外一个类A,则称类B为子类(subclass),称类A为超类(superclass)。
图1-4 派生类B继承A
C++的发明者Stroustrup认为,超类和子类这两个概念容易让人产生误解,他提出了基类和派生类这两个术语,分别用来表示超类和子类概念。本书将引用Stroustrup的术语来表示派生与继承的关系。图1-4是表示两个类继承关系的一个简图,表示类A是类B的基类(超类,也称为父类),类B是类A的派生类(子类)。
派生类B继承了基类A的所有特征和行为,尽管类B只定义了b1、b2两个数据成员,以及f4和f5两个成员函数。但它实际具有a1、a2、b1、b2四个数据成员,具有f1、f2、f3、f4、f5五个成员函数,其中的a1、a2、f1、f2、f3是从基类A继承得到的。
继承分为单继承和多重继承,单继承规定每个子类只能有一个父类,图1-4就是一个单继承。多重继承允许每个子类有多个父类。继承为软件设计提供了一种功能强大的扩展机制,允许程序员基于已经设计好的基类创建派生类,并可为派生类添加基类所不具有的属性和行为,极大地提高了软件复用的效率。
继承具有以下几个优点:
⊙ 继承能够清晰地体现相似类之间的层次结构关系。
⊙ 继承能够减少代码和数据的重复冗余度,增强程序的重用性。
⊙ 继承能够通过增强一致性来减少模块间的接口和界面,提高程序的易维护性。
⊙ 继承是自动传播代码的有力工具。
⊙ 继承是一种在普通类的基础上构造、建立和扩展新类的最有效手段。
4.多态
多态是面向对象程序设计语言的另一重要特征,它的意思是“一个接口,多种形态”。也就是说,不同对象针对同一种操作会表现出不同的行为。多态与继承密切相关,通过继承产生的不同类,而这些类都分别对某个成员函数进行了定义,当这些类的对象调用该成员函数时会做出不同的响应,执行不同的操作,实现不同的功能,这就是多态。