FAQ1.02 面向对象程序设计的基本特征有哪些?
答:
1、概述:
面向对象程序设计的雏形,早在1960年的Simula语言中即可发现,当时的程序设计领域正面临着一种危机:在软硬环境逐渐复杂的情况下,软件如何得到良好的维护?面向对象设计在某种程度上强调可重复性解决了这一问题。20世纪70年代的Smalltalk语言在面向对象方面堪称经典——以至于30年后的今天依然将这语言视为面向对象语言的基础。
面向对象程序设计可以被视为一种在程序中包含各种独立而又相互调用的单位和对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其他对象,因此它们都看被看作一个小型的”机器“,或者说是负有责任的角色。
目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目中广为应用。此外,支持者生成面向对象程序设计要比以往的做法更加便于学习,因为它们能够让人们更简单的设计并维护程序,使得程序更加便于分析、设计、理解。反对者在某些领域予以否认。
2.基本理论:
一项由Deborah J.Armstrong 进行长达40年之久的计算机著作调查显示出了一系列面向对象程序设计的基本理论。它们是:
2.1类
类(Class)定义了一件事物的抽象特点。通常来说,类定义了事物的属性和它可以做到的(它的行为)。举例来说,“狗”这个类会包含狗的一切基本特征,例如它的孕育、毛皮颜色和吠叫的能力。类可以为程序提供模板和结构。一个类的方法和属性被称为“成员”。我们来看一段伪代码。
类狗
开始
私有成员:
孕育
毛皮颜色
公有成员
吠叫()
结束
在这串伪代码中,我们声明了一个类,这些类具有一些狗的基本特征。关于公有成员和私有成员,请参见下面的继承性一节。
2.2对象
对象(Object)是类的实例。例如:“狗”这个类列举狗的特点,从而使这个类定义了世界上所有的狗。而莱丝这个对象则是一条具体的狗,它的属性也是具体的。狗有皮毛的颜色,而莱丝的皮毛颜色则是棕白色的。因此,莱丝就是狗这个类的一个实例。一个具体对象属性的值被称为它的“状态”。(系统给对象分配内存空间,而不会给类分配内存空间,这很好理解,类是抽象的系统不可能给抽象的东西分配空间,对象是具体的。)
假设我们已经在上面定义了狗这个类,我们就可以使用这个类来定义对象:
定义莱丝是狗
莱丝.皮毛颜色:=棕白色
莱丝.吠叫()
我们无法让狗这个类去吠叫,但是我们可以让对象“莱丝”去吠叫,正如狗可以吠叫,但没有具体的狗就无法吠叫。
2.3方法
方法(Method)是一个类能做的事情,但方法并没有去做这件事。作为一条狗,莱丝是会吠叫的,因此,“吠叫()”就是它的一个方法。与此同时,它可能还会有别的方法,例如“坐下()”,或者”吃()”。对一个具体对象的方法进行调用并不影响其它对象,正如所有的狗都会叫,但是你让一条狗叫不代表所有的狗都叫。如下例:
定义莱丝是狗
定义泰尔是狗
莱丝.吠叫()
则泰尔是不会吠叫的,因为这里的吠叫只是对对象“莱丝”进行的。
2.4消息传递机制
一个对象通过接收消息、处理消息、传出消息或使用其他类的方法来实现一定的功能,这叫做消息传递机制(Message Passing)。
2.5继承性
继承性(Inheritance)是指,在某种情况下,一个类会有“子类”。子类比原本的类(称为父类)要更加具体化,例如,“狗”这个类可能会有它的子类“牧羊犬”和“吉娃娃犬”。在这种情况下,“莱丝”可能就是牧羊犬的一个实例。子类会继承父类的属性和行为,并且也可包含它们自己的。我们假设“狗”这个类有一个方法叫做“吠叫()”和一个属性叫做“皮毛颜色”。它的子类(前例中的牧羊犬和吉娃娃犬)会继承这些成员。这意味着程序员只需要将相同的代码写一次。在伪代码中我们可以这样写:
类牧羊犬:继承狗
定义莱丝是牧羊犬
莱丝.吠叫() /* 注意这里调用的是狗这个类的吠叫属性。 */
回到前面的例子,“牧羊犬”这个类可以继承“皮毛颜色”这个属性,并指定其为棕白色。而“吉娃娃犬”则可以继承”吠叫()”这个方法,并指定它的音调高于平常。子类也可以加入新的成员,例如:“吉娃娃犬”这个类可以加入一个方法叫做“颤抖”。设若用“牧羊犬”这个类定义了一个实例“莱丝”,那么莱丝就不会颤抖,因为这个方法是属于吉娃娃的,而非牧羊犬。事实上,我们可以把继承理解为“是”。例如,莱丝“是”牧羊犬,牧羊犬“是”狗。因此莱丝既继承了牧羊犬的属性,又继承了狗的属性。我们来看伪代码:
类吉娃娃犬:继承狗
开始
公有成员:
颤抖()
结束
类牧羊犬:继承狗
定义莱丝是牧羊犬
莱丝.颤抖() /* 错误:颤抖是吉娃娃的成员方法 */
当一个类从多个父类继承时,我们称之为“多重继承”。多重继承并不总是被支持的,因为它很难理解,又很难被好好使用。
2.6封装性
具备封装性(Encapsulation)的面向对象程序设计隐藏了某一方法的具体执行步骤,取而代之的是通过消息传递机制传送消息给它。因此,举例来说,“狗”这个类有“吠叫()”的方法,这一方法定义了狗具体该通过什么方法吠叫。但是,莱丝的朋友蒂米并不要知道它如何吠叫。从实例来看:
/* 一个面向过程的程序会这样写: */
定义莱丝
莱丝.设置音调(5)
莱丝.吸气()
莱丝.吐气()
/* 而当狗的吠叫被封装到类中,任何人都可以简明的使用:*/
定义莱丝是狗
莱丝.吠叫()
封装是通过限制只有特定类的实例可以访问这一特定类的成员,而它们通常利用接口实现消息的传入传出。举个例子,接口能确保幼犬这一特征只能被赋予狗这一类。通常来说,成员会依它们的访问权限被分三种:公有成员、私有成员以及保护成员。有写语言更进步,Java可以限制同一包内不同类的访问;C#和VB.NET保留了为类的成员聚集准备的关键字:internal(C#)和Friend(VB.NET);Eiffel语言则可以让用户指定哪个类可以访问所有成员。
2.7多态性
多态性(Polymorphism)指方法在不同类中调用可以实现的不同结果。因此,2个甚至更多的类可以对同一消息作出不同的反应。举例来说,狗和鸡都有“叫()”这一方法,但是调用狗的“叫()”,狗会吠叫;调用鸡的“叫()”,鸡则会啼叫。
我们将它体现在为代码上:
类狗
开始
公有成员:
叫()
开始
吠叫()
结束
结束
鸡类
开始
公有成员:
叫()
开始
啼叫()
结束
结束
定义莱丝是狗
定义普斯特是鸡
莱丝.叫()
普斯特.叫()
这样,同样是叫,莱丝和普斯特作出的反映将大有不同。多态性的概念可以用在运算符重载上,本文不再赘述。
2.8抽象性
抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在在恰当的继承级别解释问题。举例说明,莱丝在大多数时候都被当作一条狗,但是如果想要它做牧羊犬做的事,你完全可以调用牧羊犬的方法。如果狗这个类还有动物的父类,那你完全可以视莱丝为一个动物。
3、OOP名词释意
编程范型 对于OOP的准确定义及其本意存在这不少争论。
通常,OOP被理解为一种将程序分解为封装数据及相关操作的模块而进行的编程方式,OOP中的与某种数据类型相关的一系列操作都被有机的封装到该数据类型当中,而非散放在其外,因而OOP中的数据类型不仅有着状态,还有着相关的行为。OOP理论,及与之同名的OOP实践相结合创造出了一种新的编程架构;OOP思想被广泛认为是非常有用的,以至于一套新的编程范型被创造出来。(其它的编程范型例如函数式编程或过程序编程专注于程序运行的过程,而逻辑编程专注于引发程序代码执行的断言)
对于面向模拟系统的语言(如:SINULA 67)的研究及高可靠性系统架构(如:高性能操作系统和CPU的架构)的研究最终导致了OOP的产生。
一些专家认为Object-Oeietation中的Object的本意来自于其在语法领域的意义,即应将其理解为“宾语”或“操作对象”,而非一般的“对象”或“物件”。我们所见到的软件运行的请求通常是Subject-Oriented,即“面向主语的”或“面向操作者的”,然而这样将使得对操作者对象的设计变得困难而复杂。有鉴于此,部分研究人员开始了对“面向操作对象”的思考。这又一次产生了信的编程范型,这是前边提到的“面向操作者”的思考模式的一向革新。
依照“面向操作对象”的原则,在程序语句中的动词应该被划分到操作对象的类型之中,而与该动词请求相关的逻辑关系也就因此将在操作对象中处理。以下是采用“面向操作对象”的方式翻译“面向操作者”的一些例子:
面向操作者:销售系统保存交易记录。
面向操作对象:交易记录在接受到销售系统的一条请求消息后将自身保存。
面向操作者:销售系统打印收据。
面向操作对象:收据在接收到销售系统的一条请求消息后将自身打印。
4、面向对象的语言
支持部分或绝大部分面向对象特性的语言即可称为基于对象或面向对象的语言。
早起,完全面向对象的语言主要包括Smalltalk等语言,目前较为流行中的语言中有Java、C#、Eiffel等。随着软件工业的发展,比较早的面向过程的语言在近些年的发展中也纷纷吸收了许多面向对象的概念,比如:C->C++,BASIC->Visual Basic->Visual Basic.NET,Pascal->Object Pascal,Ada->Ada95。
5.历史
计算机科学中对象和实例概念的最早萌芽可以追溯到麻省理工大学的PDP-1系统。这一系统是最早的基于容量架构(capability based architecture)的实体系统。另外1963年lvan Sutherland的Sketchpad应用中也也蕴涵了同样的思想。对象作为编程实例最早是与1960年代由Sinula 67语言引入思维,Sinula这一语言是Ole-Johan Dahl和Keisten Nygaard在挪威奥斯陆计算机中心为模拟环境而设计的。(据说,他们是为了模拟船只而设计的这种语言,并且对不同船只间属性的相互影响感兴趣。他们将不同的船只归纳为不同的类,而每一个对象,基于它的类,可以定义自己的属性和行为。)这种办法是分析式程序的最早概念体现。在分析式程序中,我们将真是世界的对象映射到抽象的对象,这叫做“模拟”。Simula不仅引入了“类”的概念,还应用了实例这一对象——这可能是这些概念最早的应用。
20世纪70年代施乐PARC研究所发明了Smalltalk语言将面向对象程序设计的概念定义为,在基础运算中,对对象和消息的广泛应用。Smalltalk的创建者深受Sinula 67的主要思想影响,但Smalltalk中的对象是完全动态的——它们完全可以被创建、修改并销毁,这与Simula中的静态对象有所区别。此外,Smalltalk还引入了继承性的思想,它因此一举超越了不可创建实例的程序设计模型和不具备继承性的额Simula。
此外,Simula 67 的思想亦被应用在许多不同语言,如Lisp、Pascal。
面向对象程序设计在80年代称为了一种主导思想,这主要归功于C++——C语言的扩充版。在图形用户界面(GUI)日渐崛起的情况下,面向对象程序设计很好的适应了潮流。GUI和面向对象设计的紧密关系在Mac OS X中可见一斑。Mac OS X是由Object-C语言编写成的,这一语言是一个仿Smalltalk的C语言的扩充版。面向对象程序设计的思想也是事件处理式的程序设计更加广泛的被应用(虽然这一概念并非仅存在于面向对象程序设计)。一种说法是,GUI的引入极大的推动了面向对象程序语言设计的发展。
在ETH Zurich(英文),Niklaus Wirth和他的同事们对抽象数据和模块化程序进行了调查。Modula-2将这些都包括了进去,而Oberon则包含了一种特殊的面向对象方法——不同于Smalltalk与C++。
面向对象的特性也被加入了当时较为流行的语言:Ada、BASIC、Lisp、Fortran、Pascal以及种种。由于这些语言最初没有面向对象的设计,故而这种柔和常常会导致兼容性和维护性的问题。与之相反的是,“纯正的”,面向对象语言却缺乏一些程序员赖以生存的特性。在这一环境下,开发新的语言成了当务之急。作为先行者,Eiffel成功地解决了这些问题,并成了当时较受欢迎的语言。
在过去的几年中,Java语言称为了广为应用的语言,出了它与C和C++语法上的相似性。Java的移植性是它的成功中不可磨灭的一步,因为这一特性,以吸引了庞大的程序员群的投入。
近日,一些既支持面向对象程序设计,又支持面向过程程序设计的语言悄然浮出水面。它们中的佼佼者有Python和Ruby等等。
正如面向过程程序语言设计使得结构化程序语言设计的技术得以提升,现代的面向对象程序设计方法使得对设计模式的用途、契约式设计和建模语言(如UML)技术也得到了一定提升。
5.1脚本中的OOP
近年来,面向对象程序设计越发流行于脚本语言。Python和Ruby是建立在OOP原理的脚本语言,Perl和PHP亦分别在Perl 5和PHP 4时加入面向对象特性。