3.1 概述
当采用面向对象程序设计的思想进行编程时,首先对问题进行面向对象分析(Object Oriented Analysis,OOA),它是从世界观的角度去分析问题,认为世界是由各种各样具有自己的运动行为和内部状态的对象所组成,不同对象之间的相互作用和通信构成了完整的世界。了解该问题所涉及的对象、对象之间的关系和作用,抽象该问题的对象模型,使这个对象模型能够真实反映出问题的本质。针对不同的问题性质选择不同的抽象层次,了解和解决问题的本质属性。接着进行面向对象设计(Object Oriented Designing,OOD),它是从方法论的角度去设计对象模型,围绕世界中的对象构造系统,而不是围绕功能构造系统。根据所应用的面向对象软件开发环境不同,在对问题的对象模型分析的基础上,对其进行修正,在软件系统内设计各个对象、对象之间的关系和通信方式等。最后进行面向对象程序设计(Object Oriented Programming,OOP),它是从程序实现的角度去解决问题,程序对象应该是将组成对象的状态数据代码和对象具备的行为功能代码封装成一个整体,符合强内聚和弱耦合的原则。实现对象的内部功能,确定对象的哪些功能在哪些类中描述,确定并实现程序的界面、输出形式及其他控制机构等,完成在面向对象设计阶段所规定的各个对象应该完成的任务。
3.1.1 面向对象程序设计的基本概念
1.对象与实例
顾名思义,面向对象的程序设计是以对象(Object)为核心,以对象所拥有的数据状态和功能行为为工具解决特定问题。对象是理解面向对象技术的关键,现实世界中到处存在对象的例子,例如:一条狗、一辆汽车、一台电视机等。每个对象都有两个属性:状态(State)和行为(Behavior)。例如:狗具有名字、颜色和是否饥饿等状态以及跑动、吠叫和抓老鼠等行为。汽车具有当前速度、当前档位和当前油量等状态以及加速、换挡和加油等行为。这些对象的状态和行为还有可能包含其他对象,例如:狗抓老鼠的行为中包含了老鼠对象。
如果把现实世界中的对象转化到面向对象编程的世界中,就概念化为软件对象。与现实世界中的对象概念相对应,软件对象也具有状态和行为两个属性,对象把它的状态存储在数据字段(Field)(有些程序设计语言笼统地称为变量)中,通过方法(Method)(有些程序设计语言笼统地称为函数)操作它的功能行为。仅仅使用状态和行为并不能区分不同的对象,例如:两只同一厂商出产的座钟指示相同的时间时,就很难区分它们。为此,在软件对象的属性中加入标识(Identifier)来区分不同的对象,每个对象都仅有属于它自己的唯一的标识,软件对象通常用对象名来表示。
总之,面向对象程序设计中的对象具有标识、状态和行为三个属性,有些程序设计语言笼统地分别用对象名、变量和方法来表示。在使用面向对象的程序设计语言编程时,只要定义了对象名、对象的内部变量和方法,就可以创建一个对象。
实例(Instance)是对象的具体体现,是对象在现实世界的具体事物。当把某一类对象创建出来以后,对应的每一个实际事物就称为该对象的实例化。
2.类与抽象
在现实世界中,许多对象个体其实都是同一种类型。例如:可能有成千辆汽车,它们的制造商和型号都相同,每辆汽车都出于相同的制造模型,因此包含相同的组件。在面向对象的理论上,“汽车”就可以被称为“汽车类”的一个实例对象,“类”就是创建对象个体的制造模型,它是同类对象的集合与抽象。
面向对象程序设计就是从大量的实际对象中抽象出它们的共同点,这些共同点称为“类(Class)”,让这些类成为通用的程序模板,把它们组合起来设计通用软件解决现实世界的复杂问题。从这个意义上来说,类是一种抽象的数据类型,它是所有具有一定共性的对象的抽象,而属于类的某一个对象则是类的一个实例。实际上,在程序中编写的都是用户定义的类,即定义同类对象公共的属性,然后再用对象名创建类的实例:对象。
3.消息
在面向对象程序设计过程中,对象在完成特定功能时,以及对象与对象之间进行联系时,都是通过消息传递来实现的。消息(Message)用来请求对象执行某一处理或回答某些信息的要求,它统一了数据流和控制流。某一对象在完成特定功能时,如果需要,它可以通过传递消息请求其他对象完成某些处理工作或回答某些信息。其他对象在执行所要求的处理活动时,同样可以通过传递消息与别的对象联系。因此,程序的执行是依赖对象之间传递消息完成的。
发送消息的对象称为发送者,接收消息的对象称为接收者。一个消息通常包含三个方面的内容:接收消息对象的名称,接收对象应完成的操作方法,方法所需要的参数。消息中只包含发送者的要求,它告诉接收者需要完成哪些处理,但并不关心接收者应该怎样完成这些处理。一个对象能够接收不同形式、不同内容的多个消息,相同形式的消息可以传递给不同的对象,不同的对象对于形式相同的消息可以得出不同的结果。对于传递来的消息,对象可以返回相应的应答信息,这种返回不是必需的。
当一个面向对象的程序运行时,通常要完成三个步骤:首先,根据需要创建对象;其次,当程序处理信息或响应来自用户的输入时,要从一个对象传递消息到另一个对象(或从用户到对象);最后,当不再需要该对象时,应删除它并回收它所占有的内存单元。
3.1.2 面向对象程序设计的特点
1.封装
通过公共的方法操作对象的内部私有数据字段,并且作为对象传递消息的主要机制,隐藏内部数据字段,要求所有交互操作都通过对象的方法来实现,称为数据封装(Encapsulation)。封装反映了事物的独立性,现实世界中的一切实体对象几乎都是封装的,一般情况下,只能看到这些实体的外壳而不能看到其内部结构,所以每个实体对象都是封装好的内聚性很强的个体。
在面向对象程序设计中,封装的原则就是将对象的内部结构对外做信息隐藏,让外部不可访问,但提供一系列的公有接口,用来与其他对象进行消息传递。封装的特点如图3-1所示。
图3-1 封装的特点
2.继承
不同类型的对象相互之间经常具有某些共性。例如:轮船和客轮是两种类型,轮船具有吨位、时速等数据字段,具有行驶、停止等功能方法;客轮具有轮船的全部属性,又有自己的特殊数据(载客量)和功能方法(供餐)。当轮船类型已知时,描述客船时就可以将轮船的属性全部照搬过来,只需要添加自己的特殊属性即可,此时可称客船继承了轮船的特征。
在面向对象程序设计中,继承(Inheritance)是存在于两个类之间的一种关系,当一个类拥有另一个类的所有数据和方法时,就称这两个类之间具有继承关系,被继承的类称为超类或父类,继承了超类或父类所有属性的类称为子类。使用继承能够使得程序结构清晰,降低程序编写和维护的工作量。
在面向对象的继承特征中,有多重继承和单重继承之分。多重继承是指一个类可以有一个以上的直接超类或直接父类,它的数据和功能从所有这些超类或父类中继承,这种继承层次是复杂的网状结构。单重继承是指任何一个类只能有一个直接超类或直接父类,这种继承层次是单纯的树状结构。单重继承示意图如图3-2所示。
图3-2 单重继承示意图
3.多态
在面向过程程序设计中,主要是编写一个一个的过程或函数,这些过程和函数各自对应一定的功能,它们是不能重名的,否则在使用过程名或函数名调用时就会产生歧义或错误。在面向对象程序设计中,“重名”现象却成为一个有力的工具。例如:“启动”功能是所有交通工具都具有的操作,但不同的交通工具,其“启动”操作的具体实现是不同的,汽车“启动”是“发动机点火—启动引擎”,轮船“启动”是“发动机点火—起锚”,热气球“启动”是“充气—解缆”。如果不允许这些目标和功能相同的程序使用相同的名字,就必须分别定义“汽车启动”“轮船启动”和“热气球启动”等多个方法,程序代码就会出现重复的情况,也就失去了继承特点的优势。为了解决这个问题,面向对象程序设计思想中提出了多态的概念。
多态(Polymorphism)是指一个程序中功能方法名字相同但实现结果不同的情况。在面向对象程序设计中,一种多态是通过子类对超类或父类方法的重写(Override)实现,另一种多态是通过在一类中定义同名方法参数不同的重载(Overload)实现。
为了实现多个功能方法应用到不同的对象上,这些方法只有在程序运行时才确定具体的对象,这样不用修改源程序就可以让同名的功能方法绑定到不同的类实现上,此时把这些方法封装到一个接口中形成规范,它允许多个方法使用同一个接口,从而导致在不同的上下文中对象的执行代码可以不一样。例如:汽车类、轮船类和热气球类都具有启动操作、加速操作和转向操作,方法名字相同,但具体实现不同。接口多态示意图如图3-3所示。
图3-3 接口多态示意图
总之,面向对象的问题求解就是力图从实际问题中抽象出这些封装了数据字段和方法的对象,通过定义属性变量和方法来表述它们的特征和功能,通过继承定义它们的属性扩展,通过定义接口来描述它们的多态属性及与其他对象的关系,最终形成一个广泛联系的可理解、可扩充、可维护和更接近于问题本来面目的动态对象模型系统。