第一章对象引论“我们之所以将自然界分解,组织成为各种概念,并总结出其重要性,主要是因为我们知道我们的语言社区所共同持有的,并以我们的语言的形式所固定下来的一种约定…除非赞成这个约定中所颁布的有关数据的组织和分类的内容,否则我们根本无法交谈。”BenjaminLeeWhorf(1897-1941)计算机革命起源于机器,因此,编程语言的起源也始于对机器的模仿趋近。但是,计算机并非只是机器那么简单。计算机是头脑延伸的工具(就象StevenJobs常喜欢说的“头脑的自行车”一样),同时还是一种不同类型的表达媒体。因此,这种工具看起来已经越来越不像机器,而更像我们头脑的一部分,以及一种诸如写作、绘画、雕刻、动画、电影等的表达形式一样。面向对象程序设计(Object-orientedProgramming,OOP)便是这种以计算机作为表达媒体的大潮中的一波。本章将向您介绍包括开发方法概述在内的OOP的基本概念。本章,乃至本书中,都假设您在过程型编程语言(ProceduralProgrammingLanguage)方面已经具备了某些经验,当然不一定必须是C。如果您认为您在阅读本书之前还需要在编程以及C语法方面多做些准备,您可以研读本书所附的培训光盘“Java基础(FoundationsforJava)”。本章介绍的是背景性的和补充性的材料。许多人在没有了解面向对象程序设计的全貌之前,感觉无法轻松自在地从事此类编程。因此,此处将引入众多的概念,以期助您建立对OOP的扎实全面的见解。然而,还有些人可能直到在看到运行机制的某些实例之前,都无法了解面向对象程序设计的全貌,这些人如果没有代码在手,就会陷于困境并最终迷失方向。如果您属于后面的这个群体,并且渴望尽快获取Java语言的细节,那么您可以越过本章——在此处越过本章并不会妨碍您编写程序和学习语言。但是,您最终还是会回到本章来填补您的知识,这样您才能够了解到为什么对象如此重要,以及怎样使用对象来进行设计。抽象过程所有编程语言都提供抽象(abstraction)机制。可以认为,你所能够解决的问题的复杂性直接取决于抽象的类型和质量。我所谓的“类型”是指“你所抽象的是什么?”汇编语言是对底层机器的小型抽象。接着出现的许多所谓“命令式(Imperative)”语言(诸如FORTRAN、BASIC、C等)都是对汇编语言的抽象。这些语言在汇编语言之上有了大幅的改进,但是它们所作的主要抽象仍要求你在解决问题时要基于计算机的结构,而不是基于你试图要解决的问题的结构来考量。程序员必须建立在机器模型(MachineModel)(位于你对问题建模所在的解空间(SolutionSpace)内,例如计算机)和实际待解问题模型(ProblemModel)(位于问题所在的问题空间(ProblemSpace)内)之间的关联。建立这种映射(Mapping)是费力的,而且它不属于编程语言的内在性质,这使得程序难以编写,并且维护代价高昂。由此,产生了完整的“编程方法(ProgrammingMethod)”产业。另一种对机器建模的方式就是对待解决问题建模。早期的编程语言,诸如LISP和APL都选择世界的某种特定视图(分别对应于“所有问题最终都是列表(List)”或者“所有问题都是算法形式的(algorithmic)”)。PROLOG则将所有问题都转换成为决策链(Chainofdecisions)。此外还产生了基于约束条件(constraint-based)编程的语言和专门通过对图形符号操作来实现编程的语言(后者被证明限制性过强)。这些方式对于它们被设计时所瞄准要解决的特定类型的问题都是不错的解决方案,但是一旦超出其特定领域,它们就力不从心了。面向对象方式(Object-orientedapproach)通过向程序员提供用来表示在问题空间中的元素的工具而更进一步。这种表示方式具有足够的概括性,使得程序员不会受限于任何特定类型的问题。我们将问题空间中的元素及其在解空间中的表示成为“对象(Object)”。(你还需要一些无法类比为问题空间元素的对象)。这种思想的实质是:程序可以通过添加新类型的对象使自身适用于某个特定问题。因此,当你在阅读描述解决方案的代码的同时,也是在阅读问题的表述。相比以前我们所拥有的所有语言,这是一种更灵活和更强有力的语言抽象。所以,OOP允许以问题的形式来描述问题,而不是以执行解决方案的计算机的形式来描述问题。但是它仍然与计算机有联系:每个对象看起来都有点像一台微型计算机——它具有状态,并且能够执行你赋予它的各种操作。如果要在现实世界中对对象作类比,那么说它们都具有特性(Characteristic)和行为(Behavior)似乎不错。AlanKay曾经总结了第一个成功的面向对象语言,同时也是Java赖为根基的语言之一的Smalltalk的五个基本特性,这些特性表现了一种纯粹的面向对象程序设计方式:1.万物皆为对象。将对象视为奇特的变量,它可以存储数据,除此之外,你还可以要求它在自身上执行操作。理论上讲,你可以抽取待解问题的任何概念化构件(狗、建筑物、服务等),将其表示为程序中的对象。2.程序是对象的集合,它们彼此通过发送消息来调用对方。要想产生一个对对象的请求,就必须对该对象发送一条消息。更具体地说,你可以把消息想象为对某个特定对象的方法的调用请求。3.每个对象都拥有由其它对象所构成的存储。你可以通过创建包含现有对象集合的包的方式来创建新类型的对象。因此,你可以在程序中构建复杂的体系,同时将其复杂性通过对象的质朴性得以屏蔽。4.每个对象都拥有其类型(Type)。按照通用的说法,“每个对象都是某个类(Class)的一个实例(Instance)”,其中“类”就是“类型”的同义词。每个类中最重要的区别于其它类的特性就是“你可以发送什么消息给它?”5.某一特定类型的所有对象都可以接收(Receive)同样的消息。这是一句意味深长的表述,你在稍后便会看到。因为“圆形(circle)”类型的对象同时也是“几何形(shape)”类型的对象,所以一个“圆形”对象必定能够接受(accept)发送给“几何形”对象的消息。这意味着你可以编写与“几何形”交互并自动处理所有与几何形性质相关的事物的的代码。这种“可替代性(substitutability)”是OOP中最强有力的概念之一。Booch提出了一个对对象的更加简洁的描述:对象拥有状态(State)、行为(Behaviour)和标识(Identity)。这意味着每一个对象都可以拥有内部数据(它们给出了该对象的状态)和方法(它们产生行为),并且每一个对象都可以唯一地与其他对象相区分开,具体说来,就是每一个对象在内存中都有一个唯一的地址。1某些编程语言的设计者认为面向对象编程本身不足以轻松地解决所有编程问题,所以他们提倡将不同的方式结合到多重聚和编程语言中(multipleparadigmprogramminglanguage)。您可以查阅TimothyBudd的MultipleparadigmProgramminginLeda一书(Addison-Wesley1995)2这确实显得有一点过于受限了,因为对象可以存在于不同的机器和地址空间中,它们还可以被存储在硬盘上。在这些情况下,对象的标识就必须由内存地址之外的某些东西来确定了。每个对象都有一个接口亚里士多德大概是第一个深入研究类型(Type)的哲学家,他曾提出过鱼类和鸟类(theclassoffishesandtheclassofbirds)这样的概念。所有的对象都是唯一的,但同时也是具有相同的特性和行为的对象所归属的类的一部分,这种思想被直接应用于第一个面向对象语言Simula-67,它在程序中使用基本关键词class来引入新的类型。Simula,就像其名字一样,是为了开发诸如经典的“银行出纳员问题(Banktellerproblem)”这样的仿真程序而创建的。在银行出纳员问题中,有出纳员、客户、账户、交易和货币单位等许多“对象”。在程序执行期间具有不同的状态而其他方面都相似的对象会被分组到对象的类中,这就是关键词class的由来。创建抽象数据类型(类)是面向对象程序设计的基本概念之一。抽象数据类型的运行方式与内置(built-in)类型几乎完全一致:你可以创建某一类型的变量(按照面向对象的说法,称其为对象或实例),然后操作这些变量(称其为发送消息或请求;你发送消息,对象就能够知道需要做什么)。每个类的成员(member)或元素(element)都共享相同的性质:每个账户都有结余金额,每个出纳都可以处理存款请求等。同时,每个成员都有其自身的状态:每个账户都有不同的结余金额,每个出纳都有自己的名称。因此,出纳、客户、账户、交易等都可以在计算机程序中被表示成为唯一的实体(entity)。这些实体就是对象,每一个对象都属于定义了特性和行为的某个特定的类。所以,尽管我们在面向对象程序设计中实际所作的是创建新的数据类型,但事实上所有的面向对象程序设计语言都使用Class关键词来表示数据类型。当你看到类型(Type)一词时,请将其作为类(Class)来考虑,反之亦然。3既然类被描述成了具有相同特性(数据元素)和行为(功能)的对象集合,那么一个类就确实是一个数据类型,就像所有浮点型数字具有相同的特性和行为集合一样。二者的差异在于,程序员通过定义类来适应问题,而不再被强制只能使用现有的被设计用来表示在机器中的存储单元的数据类型。你可以根据需求,通过添加新的数据类型来扩展编程语言。编程系统欣然接受新的类,并且给予它们与内置类型相同的管护和类型检查(Type-checking)。面向对象方法并不是仅局限于构件仿真程序。无论你是否同意任何程序都是你所设计的系统的一个仿真的观念,面向对象技术确实可以将大量的问题降解为一个简单的解决方案。一旦类被建立,你想要创建该类的多少个对象,就可以创建多少个了,然后去操作它们,就像它们是存在于你的待解问题中的元素一样。事实上,面向对象程序设计的挑战之一,就是在问题空间的元素和解空间的对象之间创建一对一的映射。但是,你怎样才能获得对你有用的对象呢?必须有某种方式产生对对象的请求,使对象完成诸如完成一笔交易、在屏幕上画图、打开开关之类的任务。每个对象都只能满足某些请求,这些请求由对象的接口(Interface)所定义,决定接口的便是类型(Type)。以电灯泡为例来做一个简单的比喻:3有些人对此还是区别对待的,他们声称类型决定了接口,而类是该接口的一个特定实现。Lightlt=newLight();lt.on();接口定义了你能够对某一特定对象发出的请求。但是,在程序中必须有满足这些请求的代码。这些代码与隐藏的数据一起构成了实现(implementation)。从过程型编程的观点来看,这并不太复杂。在类型中,每一个可能的请求都有一个方法与之相关联,当你向对象发送请求时,与之相关联的方法就会被调用。此过程通常被总结为:你向某个对象发送消息(产生请求),这个对象便知道此消息的目的,然后执行对应的程序代码。上例中,类型/类的名称是Light,特定的Light对象的名称是lt,你可以向Light对象发出的请求是:打开它、关闭它、将它调亮、将它调暗。你以这种方式创建了一个Light对象:定义这个对象的“引用(reference)”(lt),然后调用new方法来创建该类型的新对象。为了向对象发送消息,你需要声明对象的名称,并以圆点符号连接一个消息请求。从预定义类的用户观点来看,这些差不多就是用对象来进行设计的全部。前面的图是UML(UnifiedModellingLanguage)形式的图,每个类都用一个方框表示,类名在方框的顶部,你所关心的任何数据成员(datamember)都描述在方框的中间部分,方法(隶属于此对象的,用来接收你发给此对象的消息的函数)在方框的底部。通常,只有类名和公共方法(PublicMethod)被示于UML设计图中,因此,方框的中部并不绘出。如果你只对类型感兴趣,那么方框的底部甚至也不需要被绘出。每