最近重读了经典的《Thinking in JAVA》第三版,有些感悟和想法。
编程的目的,是为了模拟现实存在的逻辑过程,并最终降低这种模拟的复杂性。这种逻辑过程的数字化过程分为两个部分,一是对汇编语言的抽象,将机器语言与实际模型进行映射;第二是选择模拟映射的方式,如选择一种特定的视图方式来模拟逻辑。如LISP将世界模拟为表,PROLOG将世界模拟为决策链。但是这些方法都限制在它的使用范围内,不具备一般性,或者说在普遍模拟中没有绝对的优势。
一般性的模拟映射,应该是所谓的OOP方法,即面向对象编程。
OOP的第一条是抽象,这种视图方式将所有存在的空间元素及其存在的空间表示为“对象object”,对象的基本特征有五种:
1.万物皆为对象
2.程序是对象的集合,对象通过消息机制互相调用
3.每个对象都拥有其它对象构成的存储,即可以通过创建对象集合的包的方法新建新类型的对象
4.每个对象都有类型
5.同一特定类型的所有对象都可以接收相同的消息。这是一个重要的特点,即所谓的可替代性(“多态”)。
对于对象的另外一种看法是:对象是一个拥有唯一标识符、状态和行为的东西。
我们该如何看待一个对象呢?对象是类的一个实例,它可以提供服务,即是一个“服务提供者”,对象的功能就是它的服务。服务通过对象的接口释放出来,而接口和服务的实现之间是隔离的。使用对象功能的程序员不能揭开对象功能的实现秘密。当然,这也是不必要的。除非你想查看源代码。
使用“代码隔离”的技术处于两种考量方式:
1.客户端程序员不需要接触的这一部分,这些代码是组合级别更低的东西,客户端的程序员的逻辑不必限制于此.
2.库的设计者的微小的改变应该不会影响客户端程序员的程序变化。
服务的获取并不是没有限制的,即对象的方法是具有不同的可访问性的。如public,private和protected三种标识符,确定了对象的方法是否能够被其它对象调用的权限。
一个对象对象的功能并不是万能的,我们常常需要扩展它以实现新的目标。对于这个目标,通常有两种方法:聚合和继承。
所谓聚合,实际是将一个对象放置到另一个对象中去,它是程序员最常用的方法,我们的绝大部分程序都是这样写成的,比如用AO开发GIS系统,大部分都是聚合对象;继承是OOP中的一个重要特点,也许是最吸引人的地方,但同时也是最滥用的部分。
从基类继承的子类对象,在扩展功能的方式上也有两种方法,其一是子类添加新的方法函数;其二是使用重载(overriding)方法,即父类和子类的方法函数名相同,但是它们的方法实现方式不一样。调用的时候,各个对象会分别调用自己的方法函数。在JAVA中,要想重载一个方法,只需要在子类中直接写出方法的新实现过程就行了。
在处理类型的层次结构的时候,我们常常将一个对象当作它的基类对象来看待,这种方法称为upcast(这是容许的,例如我们可以将三角形看成形状,但形状不一定是三角形),如圆形、方形和三角形都是形状。所有的形状都可以被绘制、擦除和移动,所有的这些方法都是直接对一个shape对象发送消息,而不用确定这个对象具体如何处理不同的消息。但是,这种方法具有一个问题,当我们泛化对象使用的时候,比如将一个三角形泛化为形状对象进行绘制时,编译器在编译的时候是无法知道哪一段代码该执行的,这应该是在执行过程中需要处理的问题。
如下面的代码:
void draw(shape e){ ...... }
Circle c=new Circle();
Line l=new Line();
draw(c);
draw(l);
为了解决这个问题,JAVA采用了“后期绑定”的技术,即让编译器在运行的时候才确定使用哪一段代码。 在某些语言中,我们需要明确这种绑定,如C++使用虚函数关键字virtual来声明,而在JAVA中,后期绑定是默认的。JAVA正是用这种方式来实现了多态特性。
对于抽象类和接口这两个概念,初学者就会开始迷糊。在前面的内容中,我们强调了继承关系,这种关系中的父类往往成为一个UPCAST的对象而非一个实际服务的提供者,即它只是为了“泛型”而存在,可以不需要提供任何的实际功能。既然它的使命如此,JAVA就提供了两种抽象化方式,一是abstract class,抽象类中有抽象方法。这个类型的类看起来和别的类没有什么差别,但是,它不能派生出对象,它需要对象干什么呢?它是为了概念而生,而不是为了实现而生的。从抽象类继承的子类必须实现父类中定义的抽象方法,否则子类仍然是抽象类。 至于接口,这个问题复杂的多。在这里就不谈论了。
(本文已被浏览 次)

相关评论
