您的位置:首页 > 编程语言 > Java开发

Thinking in Java阅读笔记(一)

2014-10-31 17:10 225 查看
第一章:对象导论

抽象化的过程

汇编语言仅对底层的实体机器进行少量抽象化。许多所谓命令式程序语言(Fortran、BASIC、C),则在汇编语言之上再抽象化。此类语言大幅改进了汇编语言,但他们所做的主要是机器本身的抽象化,你依旧无法逃脱“以电脑结构进行问思考”的命运,因而无法以待解决问题的结构来作为思考基准。程序设计者必须自行建立介于机器模型和实际待解问题模型之间的关联性。这里头需要的便是“对映”功夫,然而这种能力并非程序语言的自性本质,这使得程序难以撰写,维护代价高昂。

个人理解:非面向对象的语言都是要程序员以机器的角度出发,解决实际问题,要把实际问题转换成适应机器的方式,来让机器解决。

面向对象法更进一步,提供各式各样的工具,让程序设计者得以在题域中表现必要的元素。我们将题域中的元素和其在解域中的表述称为“对象”。当你阅读解法程序的代码时,便如同阅读问题本身的表述一样。这种语言比我们过去所拥有的任何语言具备更弹性更威力的抽象化机制。因此OOP提供了以问题描述问题的能力,而不再是以解答执行之所在(电脑)的形式来描述问题。

重复运用实现码

想要重复运用某个class,最简单的方式莫过于直接使用其所产生的对象,此外你也可以把某个class对象置于另一个class内。我们称这种形式为“产生一个成员对象”。新的classes可由任意数目、任意型别的它种对象组成,这些对象可以任何组合方式到达你想要的功能。由于这种方式是“以既有的classes合成新的class”,所以这种观念被称为“组合(composition)或聚会(aggregation)”。组合通常被视为“has-a”的关系,就好像我们说“车子拥有引擎”。



(以上UML图以实心菱形指向车子,代表组合关系)

继承:重复运用接口


(以上UML图中的箭号,是从derived
class 指向base class,表示继承)

当你继承既有的type时,便创造了新的type,后者不仅包含前者的所有成员(private成员会被隐藏,而且无法访问),更重要的是它也同时复制了base class的接口。也就是说,所有可以发送给base class对象的消息,也都同样可以发送给derived class对象。通过继承而发送的型别等价性,是了解面向对象程序设计精髓的重要关键。
两种做法可以产生derived class与base class之间的差异。第一种做法,只要直接在derived class中增加新函数即可。这些新函数并非base class接口的一部分。这意味着base class无法满足你的需要,因此你得加入更多函数。这种既简单又基本的方式,有时候对你的问题而言是一种完美的解答。但是你应该仔细思考,你的base
class是否也可能需要这些额外功能。这种发现与更替的过程,会在整个面向对象设计过程中持续发生。
形成差异的第二种方法(也许是更重要的方法)便是改变既有之base class的函数行为,这种行为我们通常称为“覆写(overriding)”。

随多态而生的可互换对象
由non-oop编译器所产生的函数调用,会以所谓“前期绑定”方式来调用函数,运用这种方式,编译器对调用动作产生出特定的函数名称,而联结器(linker)再将此调用动作协议为“欲执行之程序代码的绝对地址”,但是在oop中,程序未到执行期是无法决定程序代码的地址的,因此当我们将消息发送给泛化对象(generic object)时,必须采用其他解决方案。
为解决上述问题,面向对象程序语言采用所谓的“后期绑定”观念。为了达到后期绑定,Java使用一小段特殊程序代码来代替调用动作的绝对形式。这一小段程序代码会通过对象内存储的信息来计算函数实体地址。Java的所有函数,除了被声明final者,皆使用后期绑定。这意味着在一般情况下,你不需要判断后期绑定动作何时发生——他会自动发生。将某个函数声明为final可以“关闭”动态绑定。
在某些程序语言里头(例如C++),你得明确指出是否希望某个函数具备后期绑定的弹性。这一类语言把所有的member functions的绑定动作缺省为“非动态”。这会引起诸多问题,所以Java将所有member functions缺省绑定为动态绑定(后期绑定),你不需要加上任何关键字,就可以获得多态(polymorphism)的威力。
我们把“将derived class 视为其 base class”的过程,称为“向上转型upcasting”。

抽象基类与接口
通常在一个设计案中,你会希望base class仅仅代表其derived class 的接口。也就是说,你不会希望任何人产生base class的实际对象,而只希望他们向上转型至base class——这使得其接口可以派上用场。如果这确实是你的愿望,可以使用关键字abstract来标示某种class是“抽象的”。你也可以使用abstract来描述“目前尚未实现完全”的函数。
关键字interface又更进一步的发挥抽象概念,阻止任何一个函数被定义出来。interface是个常被用到而又十分方便的工具,因为它提供了“接口和实现分离”的完美境界。此外,如果你想要,你可以将多个接口组合在一起,不过你无法同时继承多个一般的或抽象的classes。

对象的形貌与寿命
对象的数据存于何处?它的寿命如何控制?这里有一些不同的处理哲学。C++认为效率是最重要的议题,因此将抉择留给程序控制者。想要取得最好的执行速度?没问题,请将对象置于stack或静态存储区中,于是程序撰写时便决定了对象的存储空间和寿命。这种安排是把重点摆在存储空间的分配与释放的速度上。某些情况下这样的安排可能很有价值,不过这么做也牺牲了弹性,因为你必须在程序撰写期间明确知道对象的数量、寿命、型别。
第二种方法是从一块名为heap(堆)的内存中动态产生对象。在这种方式下,除非等到执行期,否则你无法回答需要多少对象、寿命如何、确切型别为何等问题。这些问题只有程序执行时方能给出答案。
还有一个议题就是对象的寿命。在那些“允许对象诞生于stack内”的程序语言中,编译器会判断对象应该存活多久,并可自动消灭之。但如果在heap之中生成对象,编译器对其寿命将一无所知。以C++为例,你得自行撰写程序代码来摧毁对象。因此如果不能正确做好此事,就会引发内存泄露(memory leaks),这几乎是C++程序的共通问题。Java 提供了所谓“垃圾回收器”机制,当对象不再被使用,会被自动觉察并消灭。垃圾回收器十分便利,因为它可以减少你需要考量的因素,也减少你必须撰写的程序代码数量,更重要的是,垃圾回收器提供了更高阶的保障,避免隐晦的内存泄露问题发生。

单根继承体系
单根继承体系保证所有对象都拥有某些功能,在整个系统里,你因此知道可以在每个对象身上执行某些基本操作。单根继承体系再加上“在heap之中产生所有对象”,大大简化了引数(arguments)传递动作(这是C++里头十分复杂的问题之一)。
单根继承体系也使垃圾回收器(内置于Java)的实现更加容易。所有必备功能都可安置于base class身上,然后垃圾回收器便可发送适当消息给系统中的每个对象。由于所有对象都会保证有执行期型别信息,所以你必不会因为无法判断对象的确切型别而陷入动弹不得的僵局。对于异常处理之类的系统级操作行为而言,这一点格外重要,并且也能为程序设计带来更佳弹性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Thinking in Java第二版