大三了,之前看过同样经典的《Java核心技术》,今年过生日的时候同学送给我这本英文版的《Thinking in JavaFourth Edition)》,在假期的时候我就想要试着翻译这本书。虽然现在已经有了中文版的,但是看了一部分之后觉得翻译的并不是很好,有些地方甚至翻译的很诡异。我知道自己还是个学生,大师的话未必每句都能深谙其意,甚至还有大量自己不了解的知识。但是也是想通过这个机会来锻炼一下自己,顺便提高自己的英语水平,嘿嘿。一些要求规范的地方我还是参考着机械工业出版社的《Java编程思想(第4版)》来翻译的,剩下的就自由发挥了。由于还有其他的学习任务,所以我计划就是一个月翻译一章的内容,前言和绪论就省了,直接从第一章开始。水平有限,很多地方也只好逐字逐句的翻译,由于是第一次翻译,有些东西还得慢慢体会,希望以后能做得更好吧。如果大家觉得我翻译的还行,那......多谢捧场,呦呵呵呵呵呵。如果看到什么错误或有不满意的地方,还请直言。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

Java编程思想(第四版)》

   

1 Java的灵魂-“对象

 

    “我们之所以将自然界分解,组织成各种概念,并按其含义分类,主要是因为我们是整个口语交流社会共同遵守的协定的参与者,这个协定以语言的形式固定下来......除非赞成这个协定中规定的有关语言信息的组织和分类,否则我们根本无法交谈。”      --Benjamin Lee Whorf(1897~1941)

   

    机械设备的产生和发展成就了计算机的诞生,而编程语言的出现也是始于对机械设备的模拟。

   

    但计算机并不只是简单的机器。计算机能够拓展人的思维(就像史蒂夫·乔布斯经常说的思维的轮子),并且是一种与众不同的能够帮助人类表达思想的工具。这么一说,就能看出计算机不像是什么机器,而更像是我们大脑的一部分,如同写作、画画、雕刻、动画、电影一样,是一种表达人类思想的途径。面向对象程序设计(Object-oriented programmingOOP)便是在使用计算机作为表达工具时的所采用一种技术(比如做动画时可以使用3D技术,写文章时采用的倒叙)。

   

    本章准备介绍一些OOP的基本概念,并且包括一些相关的开发方法。我想你在阅读本书之前已经有了一些编程的经验(不一定是C哦)。如果你觉得你还欠点儿火候,向你推荐谭浩强编写的《C语言程序设计》。

   

    本章要讲的东西是一个大的背景,以及一些补充性的材料。很多人在还没有形成一个宏观把握时就盲目地从事面向对象程序设计,然后就会发现自己有一种想死的感觉。所以在本章会讲很多概念来帮你夯实OOP的基础。当然,有些人可能没有例子就无法理解定义(就像是学数学一样,光讲定义,他可能很迷茫,举个例子就豁然开朗),这些人如果没有一些牢记于心的代码,学的学的就迷茫了。如果你就是这种想马上看到Java语言细节的人,觉得可以先跳过本章,那你跳就跳吧,没什么关系。但你终将有一天回到这里,了解对象的重要性到底何在以及如何使用对象,从而弥补你残缺的灵魂。

   

1.1 “抽象的发展

 

    所有编程语言都有抽象这一概念。当你在解决一个问题时,这个问题的复杂程度取决于它抽象的类型和它抽象到了啥地步。我说的类型是指:你抽的是啥?。比如说汇编语言,它就是对底层机器的一个低层次的抽象。后来又出现了许多所谓的命令式语言(FORTRANBASICC等等),它们则是在汇编语言的基础上,再一次进行抽象。这些后生们相比汇编语言都有了较大的进步,正所谓长江后浪推前浪,一浪更比一浪高啊。然而在解决问题时,它们所做的抽象仍然要求你的思考要基于计算机的结构,而不是你正在处理的问题的结构。程序员必须建立起问题模型(存在于问题域中,也就是问题存在的位置,比如在一项业务中)和机器模型(存在于解决域中,也就是你要实现解决方案的位置,比如在计算机上)之间的联系。实现这种联系是和编程语言本身没有关系的,费时费力不讨好,导致程序难以编写和维护。而且,作为一个副产物,这种模式导致了编程方法时代的出现,而并非开发方法

   

    这时我们应该知晓,应该对问题建模而不是那机器。早期的编程语言,比如LISPAPL,它们都有着特殊的世界观(前者说问题终将变成列表,后者说问题就是算法),而Prolog则是将所有问题组成决策链。此时编程语言的产生大多是为了进行在一定约束条件下的编程以及仅仅通过操作图形符号来实现编程(后者受限到都快不行了)。所有的这些方法对于它们所面临的特定类型的问题都是行之有效的,可一旦超出其特定范围,它们就傻眼了。

   

    面向对象方法的改进之处在于它提供给程序员一些能够体现出问题域(回忆之前所说)的元素的工具。这种体现能力在绝大部分情况下都足以让程序员们不再受限于问题的特定类型。我们把问题域中的元素和它们在解决域中的表现形式称为对象。(当然,你还需要一些无法比作问题域元素的对象,比如一些巧妙的设计模式)这种思想的实质是:程序可以通过添加新的对象类型来适应特定的问题,所以当你在阅读解决方案的代码时,同时也看到了对于问题的描述。这种对于语言的抽象,比我们之前见到的更强大、更灵活。所以,OOP能够让你就事论事,而不用通过说明你要在怎样的计算机上运行解决方案来描述问题。但它仍和计算机有着一些联系:每个对象有点象一台小计算机,它有状态,还有用户可以要求它执行的操作。可这并不意味着它和现实世界中的对象相比还差的远呢,因为现实世界中的对象也具有类似的特征行为啊。

   

    Alan Kay总结出第一个成功的面向对象语言Smalltalk的五条基本特性,而Smalltalk也是Java基于的语言之一。这些特性表现出了面向对象程序设计的灵魂所在:

   

    1)万物都是对象。

    让我们来把对象当成一个非常牛逼的变量吧!它能存数据,你能对它提要求,让它对自身做一些处理,就像是君要臣死,臣不得不死。理论上说呢,你可以把任何存在于现实世界中的概念性的事物表现为对象,比如说狗、房子、服务等等。

   

    2)所谓程序就是一群对象通过传递消息来告诉彼此应该干什么。

    为了向一个对象做出请求,你要向它发送一条消息。具体点说,你可以把消息当成是请求调用某个对象的方法。这一条其实就是在说“uses-a”关系,即对象之间的彼此利用。

   

    3)每个对象都能以自己特有的形式存储其他对象。

    换句话说,你可以通过包含已有的对象来创建一种新的对象。因此你可以在程序中建立复杂的体系并将其掩藏在对象这个看似简单的概念的背后。放在现实中,这似乎意味着笑里藏刀或者是深藏不漏。这一条其实就是在说“has-a”关系,即对象之间的聚合。

   

    4)每个对象都有其类型。

    这句话说的是,每个对象都是一个类(class)的实例,这里的类型是同义词。类和类之间最重要的区别就是你能向它传递什么样的信息?。放在现实中,就好比人类的实例,鸟类的实例,而人类和鸟类的区别在于,人类只能听懂人话,鸟类只能听懂鸟语。

   

    5)某一类型的所有对象都可以接收同样的信息。

    这句话非常耐人寻味,在随后的介绍中你会深谙其意。比如说一个对象的类型是圆形,那它自然也是几何形,所以圆形肯定也能接收发给几何形的消息,比如求表面积。这就意味着你可以编写代码让几何形处理所有与几何形性质相关的事务。这种可替代性(substitutability)是OOP的一大亮点。其实这一条就是在说“is-a”关系,即类之间的继承,圆形继承自几何形,关于继承的概念会在后面提到。

   

    Booch同志给出了对象的一个更加精辟的描述:

   

        对象具有状态、行为和标识。

       

    这意味着每个对象有其内部数据(赋予其状态),方法(赋予其行为),并且每个对象都能唯一的区别于其他对象(赋予其标识),更细致的来说,每个对象都有其唯一的内存地址。

   

1.2 每个对象都有个接口(至少一个)

 

    亚里士多德大概是深入研究类型type)这一概念的第一人。他曾提到鱼类和鸟类这样的概念。这种思想指出每一个对象虽然是唯一的,却又和那些具有同样特性和行为的对象同属于一个类(class)的一部分。这种思想直接被第一个面向对象语言Simula-67所采用,Simula-67通过基础关键字class向程序中引入一个新的类型。

   

    Simula,恰如其名,它的产生正是为了开发出能够模拟(模拟一词的英文为simulation)诸如银行出纳员问题这样经典问题的程序,在这个问题中你将遇到大量的出纳、客户、账户、交易以及货币单位等等各种对象。在程序运行期间,除了状态不同而其他地方都相似的对象会被分组到它们所属的类中,这便是关键字class的由来。创建抽象数据类型(也就是类)是面向对象程序设计中的基本概念之一。抽象数据类型的运行方式与内置类型(built-in types)几乎完全一致:你能创建某种类型的变量(面向对象术语中称作对象实例),可以操纵它们(称作发送消息或请求;你发送消息给一个对象,它就能知道该干啥,也就是能作出响应)。每一个类的成员们(或者说是元素)都有一些共性:比如每个账户都有余额,每个出纳员都可以接收一笔存款,等等。而每个成员又都有它自己的状态:每个账户的余额都不一样,每个出纳员都有自己的名字。因此,在计算机程序中,出纳员啦,客户啦,账户啦,交易啦,等等等等,每一个都可以被一个唯一的实体所表示。这个实体就是对象,每个对象都属于一个特定的类,这个类定义了它的特性和行为。

   

    尽管我们在面向对象编程中真正做的是添加新的数据类型(type),实际上所有的面向对象程序设计语言都用的是class这个关键字,所以当你看到类型就当成,看到就当成类型就行了。(有些人认为类型和类的不同之处在于类型决定了接口,而类是对这个接口的一个实现)

   

    因为一个类描述了一个拥有公共特性(数据元素)和行为(函数方法)的对象集合,所以它实际上就是个数据类型,比如你看浮点型数字,也有共有的特性和行为集合。不同之处在于,一个程序员为了处理一个问题而定义一个类型,而不是被迫使用一个现有的用来表示机器中一个存储单元的数据类型。你基于你的特定需要,通过添加新的数据类型扩展了编程语言,而编程系统也欢迎新类的加入并且给予它们和内置类型同样的管理和类型检查。

   

    面向对象方法并不仅仅用于建立模拟程序。任何问题都是你正在设计的系统中的一个模拟,不管你信不信,采用OOP技术能够很容易地为大量难题产生一个相对简单的解决方案。

   

    当一个类建立好了,这个类的对象你想建多少就建多少,随你便,然后可以操作它们就好象它们是你正在处理的问题中的元素一样(好比你要杀猪,你建立了砍刀类的一个对象,你会发现这个对象真的象砍刀一样好使)。的确,在进行面向对象程序设计时所面临的挑战之一就是建立起问题域中的元素和方案域中对象的一一对应。也就是对现实世界的建模。

   

    可你咋才能弄到一个有用的对象呢?肯定是有一种途径能向对象发出一个请求,然后它才能做些什么,比如完成一笔交易,在屏幕上画点什么,亦或是打开一个开关。当然每个对象肯定是只能完成某些特定的请求(你当然不能要求一个人飞起来)。你能作出的请求便是由接口(interface)定义的而类型又决定了接口。举个简单的例子,就拿下面这个灯泡来说吧:

 

   
   

    接口决定了你能对某个特定的对象所作出的请求。当然,肯定在某些地方有着能满足这些请求的代码。这些代码和隐藏的数据一起构成了接口的实现(implementation)。从程序设计的角度看,这些话并不是那么复杂。一个类型,对于每一个可能的请求都有一个对应的方法,当你对一个对象作出一个特定的请求,对应的方法就会被调用。总结以上所说就是,你向一个对象发送一个消息(作出一个请求),这个对象推测出这个请求想要干啥,然后执行某些代码。

   

    上面的例子中,类型(类)的名字是Light。这个Light类的一个对象的名字是lt,你可以对一个Light类型的对象作出打开、关闭、调亮、调暗这样的请求。你通过定义一个引用(referencelt,并且调用new关键字来获得Light类型的一个新的对象。向对象发送请求的时候,你需要写出对象的名字,然后通过一个“.”来将对象连接到这条消息上(正如例子中的lt.on()那样,向lt传递打开这一消息)。从用户预定义类的角度看,最爽的就是使用这些类的对象来编程。

   

    上面例子中的那张图是根据统一建模语言(Unified Modeling LanguageUML)的格式画的。一个大的方框代表一个类,类名写在方框的最上面的格子里,数据成员(data members)写在中间格子里,方法(methods)写在最下面的格子里(方法其实就是一个对象包括的函数,这些函数负责接收你向对象发出的任何消息)。通常用UML设计的图中只显示对象的名字和公共方法(public methods),就像例子中的一样,没有画出中间部分。如果你只对类名感兴趣,那你干脆连最下面那部分也不用画了。

   

1.3 每个对象都提供服务

 

    当你在试图开发或理解一个程序的设计时,最好的一个方法就是把对象想成服务提供者。你的程序本身就是在向用户提供服务,通过调用其他对象提供的服务来实现程序的目的。你的目标就是为你要解决的问题创建(更好的方式是重用已有的代码库)一系列能提供理想服务的对象。比方说你在盖房子的时候会需要木材水泥石料,而这些对象你或许不需要自己生产,因为可以在建材市场买到。

   

    你现在有了目标了,那该如何实现呢?一种方式就是先问问自己如果我现在已经能够将现实世界中的事物抽象为对象,哪些对象恰好能解决我的问题?假设你正在建立一个记账系统。你应当能想到一些能提供已经设计好的输入窗口的对象,还需要一些对象能够执行有关记账系统的计算,当然还得有一个对象能处理在各种打印机上打印支票和×××的任务。也许有的对象已经有了,如果没有,它们应该是什么样子的呢?它们会提供什么样的服务?它们又需要哪些其他对象来完成它们的任务呢?如果你坚持这样的思考,那么最终你会说哦!那个对象好像很简单,我现在就可以坐下来写出它的代码了!或者是恩,我肯定那个对象已经存在了。这是一种将一个问题分解为一系列对象的合理方法。

   

    把对象看成是服务提供者还有另外一个好处:有助于提高软件的内聚性。高内聚是软件设计的基础质量要求之一,它意味着软件构件(比如一个对象、一个方法,或者是一个对象库)的各个部分组合得很好。人们在设计对象时经常遇到的一个问题就是把过多的功能挤到一个对象里。比如你在设计打印支票这个模型时,你可能会觉得应该有一个对象知道各种关于格式和打印的信息。然后你就会发现仅用一个对象的话,这些功能就显得太多太拥挤了,你需要的是更多的对象。比如一个对象作为所有可能的支票排版方式的目录,可以从其中查到关于如何打印一张支票的信息;一个或者更多对象可以作为一个通用的打印接口,知道各种打印机的信息(这些对象不应当知道任何关于记账的信息,这些对象应当是从商家购买而不是自己编写);当然你还需要一个对象利用其它两个对象提供的服务来完成打印支票这个任务。因此,每一个对象都有一个内聚的服务集合。在一个良好的面向对象设计中,一个对象很重要的一点就是要做到安分守己、恪守本分,不要试图掌控一切。从上面的例子可以看到,这样做不仅可以通过购买来获得一些对象(打印机接口对象),还可以创建一些能在其他地方复用的对象(支票排版目录对象)。比如一个完整的人就相当于一个集所有功能于一身的对象,一个肾是内聚其服务的对象,如果你去做肾移植手术,你只能复用一个肾,而不能复用一个完整的大活人。

   

    把对象当成服务提供者能省好多事。这一点不仅体现在设计过程中,还体现在当其他人试图理解你的代码或是重用一个对象时。如果他们能根据对象所提供的服务看到这个对象的价值所在,那在设计的时候就可以做到灵活运用。