面向对象概述

在程序开发初期,人们使用结构化开发语言。随着软件的规模越来越庞大,结构化语言的弊端也逐渐暴露出来,开发周期越来越长,产品的质量也不尽如人意。这时人们开始将另一种开发思想引入程序中,即面向对象的开发思想。面向对象思想是人类最自然的一种思考方式,它将所有预处理的问题抽象为对象,通过了解这些对象具有哪些相应的属性以及展示这些对象的行为,以解决这些对象面临的一些实际问题。在程序开发中引入面向对象设计的概念,其实质上就是对现实世界中的对象进行建模操作。

对象

现实世界中,随处可见的一种事物就是对象。对象是事物存在的实体,如人、书桌、计算机、高楼大厦等。人类解决问题的方式总是将复杂的事物简单化,于是就会思考这些对象都是由哪些部分组成的。通常都会将对象划分为两个部分,即静态部分与动态部分。顾名思义,静态部分就是不能动的部分,这个部分被称为 “属性”,任何对象都会具备其自身属性,如一个人,其属性包括高矮、胖瘦、性别、年龄等。动态部分即对象可执行的动作,这部分被称为 “行为”,也是一个值得探讨的部分,同样对于一个人,其可以哭泣、微笑、说话、行走,这些都是这个人具备的行为。人类通过探讨对象的属性和观察对象的行为来了解对象

在计算机的世界中,面向对象程序设计的思想要以对象来思考问题,首先要将现实世界的实体抽象为对象,然后考虑这个对象具备的属性和行为。例如,现在面临一只大雁要从北方飞往南方这样一个实际问题,试着以面向对象的思想来解决这一实际问题。步骤如下:

  1. 从这一问题中抽象出对象,这里抽象出的对象为大雁。

  2. 识别这个对象的属性。对象具备的属性都是静态属性,如大雁有一对翅膀、黑色的羽毛等。这些属性如图6.1所示。

  3. 识别这个对象的动态行为,即这只大雁可以进行的动作,如飞行、觅食等,这些行为都是这个对象基于其属性而具有的动作。这些行为如图6.2所示。

    image 2024 02 29 14 40 34 092
    Figure 1. 图6.1 识别对象的属性
    image 2024 02 29 14 42 00 910
    Figure 2. 图6.2 识别对象具有的行为
  4. 识别出这个对象的属性和行为后,这个对象就被定义完成了。然后可以根据这只大雁具有的特性制定这只大雁从北方飞向南方的具体方案,以解决问题。

究其本质,所有的大雁都具有以上属性和行为,可以将这些属性和行为封装起来,构成一个类,以描述大雁这类动物。由此可见,类实质上就是封装对象属性和行为的载体,而对象则是类抽象出来的一个实例,二者的关系如图6.3所示。

image 2024 02 29 14 43 00 142
Figure 3. 图6.3 描述对象与类之间的关系

不能将一个事物描述成一类事物,如一只鸟不能被称为鸟类。但如果要给某一类事物一个统称,就需要用到类这个概念。

类就是同一类事物的统称,如果将现实世界中的一个事物抽象成对象,类就是这类对象的统称,如鸟类、家禽类、人类等。类是构造对象时所依赖的规范,如一只鸟有一对翅膀,它可以用这对翅膀飞行,而基本上所有的鸟都具有“有翅膀”这个特性和飞行的技能,这样具有相同特性和行为的一类事物就被称为类,类的思想就是这样产生的。图6.3描述了类与对象之间的关系,对象就是符合某个类的定义所产生的实例。更为恰当的描述是,类是世间事物的抽象称呼,而对象则是这个事物对应的实体。如果面临实际问题,通常需要实例化类对象来解决。例如,解决大雁南飞的问题,这里只能拿这只大雁来处理这个问题,而不能拿大雁类或鸟类来解决。

类是封装对象的属性和行为的载体,反过来说,具有相同属性和行为的一类实体被称为类。例如,鸟类封装了所有鸟的共同属性和应具有的行为,其结构如图6.4所示。

定义完鸟类之后,可以根据这个类抽象出一个实体对象,最后通过实体对象来解决相关的实际问题。

在Java语言中,类对象的行为是以方法的形式定义的,对象的属性是以成员变量的形式定义的,所以类包括对象的属性和方法。有关类的具体实现会在后续章节中进行讲解。

image 2024 02 29 14 45 54 358
Figure 4. 图6.4 鸟类结构

封装

面向对象程序设计具有以下特点:封装性、继承性和多态性。

封装是面向对象编程的核心思想。将对象的属性和行为封装起来,其载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。例如,用户使用计算机时,只需要使用手指敲击键盘就可以实现一些功能,无须知道计算机内部是如何工作的。即使知道计算机的工作原理,但在使用计算机时也并不完全依赖于计算机工作原理等细节。

采用封装的思想保证了类内部数据结构的完整性,使用类的用户不能轻易地直接操作类的数据结构,只能执行类允许公开的数据。这样就避免了外部操作对内部数据的影响,提高了程序的可维护性。

使用类实现封装特性如图6.5所示。

image 2024 02 29 14 46 51 018
Figure 5. 图6.5 封装特性示意图

继承

类与类之间同样具有关系,这种关系被称为关联。关联主要描述两个类之间的一般二元关系。例如,一个百货公司类与销售员类就是一个关联,学生类与教师类也是一个关联。两个类之间的关系有很多种,继承是关联中的一种。

当处理一个问题时,可以将一些有用的类保留下来,在遇到同样的问题时拿来复用。假如这时需要解决信鸽送信的问题,我们很自然就会想到如图6.4所示的鸟类结构。由于鸽子属于鸟类,具有与鸟类相同的属性和行为,便可以在创建信鸽类时将鸟类拿来复用,并且保留鸟类具有的属性和行为。不过,并不是所有的鸟都有送信的习惯,因此还需要再添加一些信鸽具有的独特属性及行为。鸽子类保留了鸟类的属性和行为,这样就节省了定义鸟和鸽子共同具有的属性和行为的时间,这就是继承的基本思想。设计软件时,使用继承思想可以缩短软件开发的周期,复用那些已经定义好的类可以提高系统性能,减少系统在使用过程中出现错误的概率。

继承性主要利用特定对象之间的共有属性。例如,平行四边形是四边形,正方形、矩形也都是四边形,平行四边形类与四边形类具有共同特性,就是拥有4个边,可以将平行四边形类看作四边形类的延伸,平行四边形类复用了四边形类的属性和行为,同时添加了平行四边形独有的属性和行为,如平行四边形的对边平行且相等。这里可以将平行四边形类看作是从四边形类中继承的。在Java语言中,将类似于平行四边形类的类称为子类,将类似于四边形类的类称为父类或超类。值得注意的是,可以说平行四边形是特殊的四边形,但不能说四边形是平行四边形,也就是说子类的实例都是父类的实例,但不能说父类的实例是子类的实例。图6.6阐明了图形类之间的继承关系。

从图6.6中可以看出,继承关系可以使用树形关系来表示,父类与子类存在一种层次关系。一个类处于继承体系中,它既可以是其他类的父类,为其他类提供属性和行为,也可以是其他类的子类,继承父类的属性和方法,如三角形类既是图形类的子类也是等边三角形类的父类。

image 2024 02 29 14 47 51 900
Figure 6. 图6.6 图形类层次结构示意图

多态

在6.1.4节中介绍了继承、父类和子类,其实将父类对象应用于子类的特征就是多态。依然以图形类来说明多态,每个图形都拥有绘制自己的能力,这个能力可以被看作是该类具有的行为,如果子类的对象被统一看作是父类的实例对象,那么在绘制图形时,只需调用父类也就是图形类绘制图形的方法就可以绘制任何图形,这就是多态最基本的思想。

多态性允许以统一的风格编写程序,以处理种类繁多的已存在的类及相关类。该统一风格可以由父类来实现,根据父类统一风格的处理,可以实例化子类的对象。由于整个事件的处理都只依赖于父类的方法,因此日后只要维护和调整父类的方法即可,这样就降低了维护的难度,节省了时间。

提到多态,就不得不提抽象类和接口,因为多态的实现并不依赖于具体类,而是依赖于抽象类和接口。

再回到绘制图形的实例上。图形类作为所有图形的父类,具有绘制图形的能力,这个方法可以称为“绘制图形”,但如果要执行这个“绘制图形”的命令,没有人知道应该画什么样的图形,并且如果要在图形类中抽象出一个图形对象,那么没有人能说清这个图形究竟是什么图形,因此使用“抽象”这个词来描述图形类比较恰当。在Java语言中,称这样的类为抽象类,抽象类不能实例化对象。在多态的机制中,父类通常会被定义为抽象类,在抽象类中给出一个方法的标准,而不给出实现的具体流程。实质上这个方法也是抽象的,如图形类中的“绘制图形”方法只提供一个可以绘制图形的标准,并没有提供具体绘制图形的流程,因为没有人知道究竟需要绘制什么形状的图形。

在多态的机制中,比抽象类更方便的方式是将抽象类定义为接口。由抽象方法组成的集合就是接口。接口的概念在现实中也极为常见,如从不同的五金商店买来螺丝帽和螺丝钉,螺丝帽可以很轻松地被拧在螺丝钉上,可能螺丝帽和螺丝钉的厂家不同,但这两个物品可以轻易地被组合在一起,这是因为生产螺丝帽和螺丝钉的厂家都遵循着统一的标准,这个统一的标准在Java中就是接口。依然拿“绘制图形”来说明,可以将“绘制图形”作为一个接口的抽象方法,然后使图形类实现这个接口,同时实现“绘制图形”这个抽象方法,当三角形类需要绘制时,就可以继承图形类,重写其中的“绘制图形”方法,并将这个方法改写为“绘制三角形”,这样就可以通过这个标准绘制不同的图形。