Alan Kay曾经总结了面向对象语言的五大基本特性:1)万物皆为对象。2)程序是对象的集合,它们通过发送消息来告知彼此所要做的。3)每个对象都有总结的由其它对象所构成的存储。4)每个对象都拥有其类型。5)某一特定类型的所有对象都可以接收同样的消息。
1.所有面向对象的程序设计都是由关键字class表示数据类型
1.1类描述了具有相同特效(数据元素)和行为(功能)的对象集合。
Light |
on() off() brighten() dim() |
Light lt = new Light();
lt.on();
决定interface定义的就是其类型,并不是interface来决定类型。Light这个类型,决定了他的接口就是开灯、关灯、调暗以及调亮。
2.对象的作用----提供服务
每个对象就是在提供服务的,当我们在开发或者了解一个程序设计的时候,最好的方法之一就是,将对象想象成一个服务的提供者,想象一下,这个程序就用来提供什么服务的,当你了解到其提供的服务类型,并和现有的代码库已经提供该服务或者类似的服务的程序,你就会觉得,嗷!这个程序设计真简单,copy一下,根据自己的需要更改一下,这样就可以大大的节省开发的时间,并且也能减少bug率(毕竟现有代码库的代码都是经过使用验证的)。
将对象看着服务提供者,还能提高代码的质量--提高对象的内聚性。避免在一个对象中塞入过多的功能,在后期维护变得几乎不可能,如果将不同的服务抽取出来成为不同的对象,把这些对象组合在一起就是一个软件的构成,不管是后期维护性还是扩展性都有极大的提升。
3.具体实现的隐藏
将开发人员分为类创建者和客户端程序员,客户端程序员的目的是收集各种实现快速应用开发的类,类创建者的目的是构建类。类创建者会把类的脆弱部分隐藏起来,仅暴露部分给客户端程序员。避免了现实在被粗心的或者在不知情的情况下被毁坏,减少了程序的BUG,也提升了代码的质量。
如何才能将实现隐藏起来的,这就是我们常常用到的三个控制访问的关键字:public、private、protected。这些访问指定词(access specifier)决定了紧随其后的东西可以被谁使用。在Java中还有一种默认的访问权限,这就是在没有使用前面提到的三种访问权限时,它就将发挥作用,这种访问权限被称为包访问权限。仅有在同一个包里的可以访问,包之外的这些成员就和指定了private一样。
4.具体实现的复用
类一旦被创建,那么它就是一个有用的代码单元,这项功能每次被使用的时候直接调用该类就行,而不是多次重复的创建,代码复用也是面向对象程序设计提供的最了不起的优点之一。
我们在创建新的类的时候,可以使用现有的类来组合成新的类,这种概念被称为组合(composition),如果这种组合是动态进行的,那么它通常被称为聚合(aggregation)。组合常常被视为“has-a”(拥有)关系。在这里我们引用一个书中的例子。我们已经创建了各型号引擎的类了,然后我们需要一个汽车的类,那么我们当然可以重新创建一个引擎的类还组合成为汽车,但是既然我们已经有了现有的类,最好的方法当然还是直接引用你需要的引擎,这样能减少新创建类占有的空间,提高代码的效率,这也前面提到拥有关系,汽车拥有了引擎这个类。
组合的概念,给程序的编写带来了极大的灵活性,我们创建的类常常都是private,使得使用新类的客户端程序员不能访问它们,这就可以在不干扰现有客户端代码情况下,甚至在运行时来修改这些成员对象,以实现动态修改程序的行为。
5.继承
一个简单的例子来介绍一下继承,把几何形作为一个基类,那么任何一个几何形都具有其尺寸、颜色、位置等等,在此基础上面,可以导出(继承出)具体的几何形状——圆形、正方形、三角形等,每一种具体的都具有额外的特性,但它们的基础还是继承于几何形里面,不管这些具体的图形怎么变化着色都离不开几何形的属性。
能是基类和导出类产生差异的方法只有两种,第一种,直接在导出类中添加新的方法,这些新方法并不是基类接口的一部分。第二种,也是更重要的一种,那就是覆盖(overriding),直接在导出类中创建该方法的新定义即可。
如果我们继承只覆盖基类的方法,不去添加基类中没有的新方法,这就意味着基类和导出类是完全相同的类型,它们具有完全相同的接口,一般称这种关系为“is-a”(是一个)的关系。在导出类中添加新的接口元素,这样也就扩展了接口。但这种替代并不完全,基类不能访问其新的方法。我们称这种方法为“is-like-a”(像是一个)。在这里有个和现实紧密联系的例子,在房子里安装好了冷气设备的控制器,也装好了只能制冷空调,但是当空调坏掉了,现在换上去了一台既能制冷也可以加热的空调,但是你的控制器只能控制其制冷,控制器对加热的功能一无所知,那么你会发现在这种情况下,无论如何你都需要添加一个新的方法或者接口才能是空调具有加热的功能。
6.伴随多态的可互换对象
在处理类型的层次的时候,我们一般会一个对象不当作它所拥有的特点的属性来看待,而是使用其基类的特性来处理,例如我们在处理几何形的时候,不会在意它是圆形或者三角形,这些具有具体特性的形态都被泛化(generic)为几何图形,使用的时候也是操作几何图形的性质,不去关心它是具体的性质。这样的代码是不会受到新添加的代码影响的,而且添加新类型是扩展一个面对对象程序处理新情况的最常用方式。
在面对对象程序设计中,编译器不可能产生传统意义上的函数调用。在非面向对象的时候编译器会在函数调用的时候引起所谓的前期绑定。就是在程序运行时,调用解析到被执行的代码的绝对地址。而在面向对象的程序设计时,使用了后期绑定的概念,java就是使用一小段特殊的代码来替代绝对地址的调用。在Java中,动态绑定是默认行为,不需要添加额外的关键字来实现多态。
void doSomething(Shape shape){
shape.erase();
shape.draw();
}
Circle circle = new Circle();
Triangle triangle = new Triangle();
Line line = new Line();
doSomething(circle);
doSomething(triangle);
doSomething(line);
上述代码透露出一个信息,那就是在执行doSomething()的时候,我们不关心它怎么执行的,我们知道circle(圆形)、tringle(三角形)、line(线段)都是shape(几何图形),那么你就按照几何图形都会的erase、draw去执行吧,这就是Java最经典的特性之一“多态”,编译器和运行系统会处理相关细节,你需要知道会发生什么,更重要的是怎么样通过它来设计。
7.单根继承结构
在所用的OOP语言中,所有的类最终都继承自单一的基类,这个终极基类的名字就是Object。这样的一大好处就是你的系统中的每个对象都能执行某些基本操作,所有的对象都可以很容易在堆上创建,参数的传递也得到极大的简化。
单根继承结构也是使得Java重大改进之一的垃圾回收机制的实现变得容易得多。而所有的对象都具有其类型信息,也就不会出现无法确定对象类型的僵局。这给编程带来了更大的灵活性,也是系统级操作(如异常处理)。
8.容器
容器是用来解决把你不知道需要多少空间来储存,不知道存活多久,有多少个的对象都置于这个特殊的对象类型里,让它来处理所有细节。好的OOP都有一组容器,它们被作为开发包的一部分。在C++中,容器就是标准C++类库的一部分,被称为标准模板类库(Standard Template Library,STL)。而在Java的标准类库中包含着大量的容器,如map,list,set。
没有最好的容器,只有最合适的容器。这句话不只在现实生活中有效,在程序世界里同样是有效的。ArrayList和LinkedList。这两个极其相似,但又有天壤之别。这两个容器都有着相同的接口和外部行为,但它们操作花费代价却完全不同。ArrayList在访问随机一个元素所花费的时间是固定,而LinkedList越靠近表尾所花费的时间就越多,而在表尾添加新元素表现,二者恰恰相反。
序列底层结构的不同,各类操作的效率就产生了极大的差异。如我们可以使用LinkedList来构建程序,而在优化系统性能时改用ArrayList。接口List所带来的抽象,把在容器之间进行转换时对代码产生的影响降低到最小限度。
9.参数化类型
这个在这就简单提一下,在Java SE5之前,Java通用类只有Object,所以把类型放入Object之后会丧失掉本身的特性。简而言之就是可以向上转换,但是向下基本不可能,直到泛型的出现。
10.对象的创建和生命周期
在使用对象的时候,最关键的是一个问题是,对象如何被创建及如何被销毁,每个对象被创建出来开始,就开始占用资源使用内存,当你不需要再使用这个对象的时候,你就需要对其清理销毁。
在C++中,认为效率控制是控制对象生命周期的最重要的议题,也就给了程序员提供了选择的权利。为了追求最大的执行速度,对象的存储空间和生命周期在编写程序时就被确定了下来。第一种方法,是通过对象置于堆栈或者静态储存区域来实现的,但是这些虽然效率但是牺牲了灵活性,必须要在编写程序的时候,知道对象的数量、生命周期和类型。第二种方法,在堆(heap)的内存池中动态的创建对象,这种方式是在运行时才需要知道对象有多少,什么类型等。但因为存储空间是运行时被动态管理的,所以需要大量的时间在堆中分配存储的空间。
在Java中完全采用了动态内存分配的方式,每当需要实例的时候都需要new一个对象。
C++必须通过编程的方式来确定合适销毁对象,这就造成了C++中常见的问题——内存溢出,而在Java当中,提供了“垃圾回收机制”能有有效的避免内存泄露的问题。
11.异常处理
异常错误,在程序的编写中是不可避免的,而对错误的处理也始终是最困难的问题之一。于是就有把异常错误通过编程来捕获然后抛出来的机制。值得注意的是,异常处理不是面向对象所独有的属性,早在面向对象问世之前就已经有了异常处理了。
12.并发编程
能在同一时间处理多个不能的任务,这就是并发编程的思想。多线程编程就是让不同的任务被系统指派给不同的处理器进行执行,极大的提高了效率。唯一值得注意的是:资源共享。多个任务同时访问同一个资源是,就会出现问题。
13.总结
过程语言可以用两个词来概括:数据定义和函数调用。而你可以在OOP中找到过程语言的基础概念,又加入了许多新的概念,编写良好的Java程序要比过程程序要简单得多。当然不管使用什么语言编写,语言永远都是编写程序的工具,挑选更适合需求的工具才是最好的语言。