object-oriented programming OOP 面向对象编程 将计算机看作某种表达的媒介
面向对象编程的五个特点
1. 万物皆为对象。
2. 程序是一群对象的集合,这些对象通过发送信息的方法告诉各自该做什么。可以将发送消息想象成请求调用某个对象的方法
3. 每个对象都有自己的存储空间,这些存储空间由其他对象组成。换句话说,可以通过封装现有对象,创建新对象
4. 每个对象都有一个类型。每个对象都是一个类的实例(instance),在此类(class)是类型(type)的同义词。一个类最重要的特征是“你能发送什么样的信息给它”
5. 同一类的所有对象都能接受相同的信息。由于类型为“圆”(Circle)的一个对象也属于类型为“形状”(Shape)的一个对象,所以一个圆完全能接收形状消息。这意味着可让程序代码统一指挥“形状”,令其自动控制所有符合“形状”描述的对象,其中自然包括“圆”。这一特性称为对象的“可替换性”,是OOP最重要的概念之一。
对象提供服务
尝试开发和理解程序设计时,最好的方法是将对象想象成“服务的提供者”。程序提供服务给用户,通过使用其他对象提供的服务来完成需求。目标是创建一组对象提供理想的服务来解决问题
开始这样做时要先问自己“什么样的对象可以立马解决我的问题?”例如你要开发一个记账的软件,你要构想能完成一系列事情的对象,例如显示记账界面,计算账目等等。也许有的对象已经存在,那这些对象该长什么样?提供什么样的服务,需要什么对象来实现功能?一直这样问下去,你就会觉得“对象看起来够简单了,可以直接坐下来写了”或者“这些对象一定已经存在了”。将程序解析为一组对象是很合理的做法
将对象视为服务提供者还有一个另外的好处:提高对象的凝聚性(cohesiveness)。高凝聚性(High cohesion)是软件设计质量的基本要求:这意味着软件组件的各方面能更紧密地结合在一起。设计对象时的一个通病就是往一个对象塞入太多的功能。在好的面向对象的设计中,每个对象最好只需要做好一件事,不用做太多。
隐藏实现
隐藏实现可以减少程序bug
Java使用三个显式关键字来设置类的界限:public,private和protected
权限修饰符:
访问包位置类修饰符
private protected public
本类 可见 可见 可见
同包其他类或子类 不可见 可见 可见
其他包的类或子类 不可见 不可见 可见
Java还有一个默认(default)权限,被修饰的类预设为包存取范围,即只有一个包中的类可以调用这个类的成员
重用
代码的重用是面向对象编程语言一个最重要的优点
重用类最简单的方法是直接使用该类的对象,但也可以在新类中放入该类的对象。这叫“创建一个成员对象”。新类可以由任意数量和类型的其他对象构成。这个概念叫“组织(composition)”,有时也称作包含(has-a)关系
组织具有极高的灵活性。新类的成员对象通常设为私有(private),从而能防止其他程序员使用你的类,并允许你在不影响客户代码的前提下改变成员变量。并且可以在运行过程中更改成员变量,从而动态地改变程序的行为。继承则没有这样的灵活性,因为编译器必须对通过继承创建的类加以限制。
继承虽然很重要但是会被新手程序员滥用导致程序过于复杂。如果使用组合(composition)则要简单灵活得多
继承
在继承过程中,若原始类(original class)(正式名称叫作基础类(base class)、超类(superclass)或父类(parent class))发生了变化,修改过的“克隆(clone)”类(正式名称叫作继承类(inherited class)或者子类(subclass))也会反映出这种变化。
通过继承创建的新类不仅包含了现有类型的所有成员(尽管private成员被隐藏起来,且不能访问),但更重要的是,它复制了基础类的接口。也就是说,可向基础类的对象发送的所有消息亦可原样发给衍生类的对象。根据可以发送的消息,我们能知道类的类型。这意味着衍生类具有与基础类相同的类型!为真正理解面向对象程序设计的含义,首先必须认识到这种类型的等价关系。
有两种方法将子类和父类区分开,
第一种:为衍生类添加新方法(method)或功能
第二种:重写(override)基本类原有的方法
子类继承父类,关键字extends
上溯造型(upcasting)和多态(polymorphism)
对于一系列类,我们要进行的一项重要处理就是将衍生类的对象当作基础类的一个对象对待。这一点是非常重要的,因为它意味着我们只需编写单一的代码,令其忽略类型的特定细节,只与基础类打交道。这样一来,那些代码就可与类型信息分开。所以更易编写,也更易理解
此外,若通过继承增添了一种新类型,如“三角形”,那么我们为“几何形状”新类型编
写的代码会象在旧类型里一样良好地工作。所以说程序具备了“扩展能力”,具有“扩展性”。
这种把衍生类型当作它的基本类型处理的过程叫作“Upcasting”(上溯造型)
将一条消息发给对象时,如果并不知道对方的具体类型是什么,但采取的行动同样是正确的,这种情况就叫作“多态”(Polymorphism)。对面向对象的程序设计语言来说,它们用以实现多形性的方法叫作“动态绑定”(dynamic binding)。编译器和运行期系统会负责对所有细节的控制;我们只需知道会发生什么事情,而且更重要的是,如何利用它帮助自己设计程序。
有些语言要求我们用一个特殊的关键字来允许动态绑定。在C++中,这个关键字是virtual。在Java中,我们则完全不必记住添加一个关键字,因为函数的动态绑定是自动进行的。
容器(Containers)
不同的容器满足不同需求(结合数据结构)
List(表),包含序列
Maps(映射),也叫关联数组(associative
array),关联对象和其他对象
Sets(集),含有类的每一个类型
还有队列,树和堆栈等
参数类型(Parameterized)(泛型(generics))
容器(container)所包含的对象的类型都是Object,添加到容器内的类都会被转型为Object,从而丢掉了自身的特性。当把他们取回的时候,得到的是Object而不是之前放入的类型。在这儿可以使用向下转型(downcasting),但向下转型存在风险,因为不能准确的知道要转成什么类型。但也不存在致命的危险,如果向下转型错误,运行时会抛出异常
使用参数类型机制(parameterized type mechanism),在Java中叫泛型,可以知道容器所包含的是什么类型,从而不用向下转型,避免可能发生的错误。例如:ArrayList<Shape> shapes = new
ArrayList<Shape>
对象的创建和生命周期
使用对象有一个重要的问题要解决那就是对象的创建和销毁。
那对象的数据存储在哪儿,如何控制对象的存在时间?
C++认为程序执行的效率是最重要的。为获得最快的运行速度,对象的存储和存在时间可在编写程序时决定,只需将对象放置在堆栈(有时也叫自动或定域变量(automatic or scoped variables))或者静态存储区域即可。这样便为存储空间的分配和释放提供了一个优先级。某些情况下,这种对优先级的控制很有价值。但也牺牲了灵活性,因为在编写程序时,必须知道对象的准确数量、存在时间、以及类型。如果要解决的是一个较常规的问题,如计算机辅助设计、仓储管理或者空中交通控制,这一方法就显得太局限了。
第二个方法是在一个内存池中动态创建对象,该内存池也叫“堆”或者“内存堆”(heap)。采用这种方式,在进入运行期后,才能知道到底需要多少个对象,存在时间有多长,以及准确的类型是什么。这些参数都在程序正式运行时才决定的。若需一个新对象,只需要在内存堆里创建即可。由于存储空间的管理是运行期间动态进行的,所以在内存堆里分配存储空间的时间比在堆栈里创建的时间长得多。由于动态创建方法使对象本来就倾向于复杂,所以查找存储空间以及释放它所需的额外开销不会为对象的创建造成明显的影响。除此以外,更大的灵活性对于常规编程问题的解决是至关重要的。
Java专门使用动态内存分配。每次要创建对象,都要使用新的操作符(operator)创建对象的动态实例
还要考虑另外一个问题,即对象的生命周期(lifetime)。若在堆栈或者静态存储空间里创建一个对象,编译器会判断对象存在多长时间,然后自动的销毁对象。然而在堆里创建对象,那么编译器不清楚其生命周期。在C++中可以程序化地(programmatically)决定何时销毁对象,如果没有正确操作会导致内存泄露(memory leak)。Java提供“垃圾收集器”(garbage collector)自动发现对象何时不再使用然后销毁它。垃圾收集器很方便,更重要的是垃圾收集器能防止内存泄露的问题
客户/服务器计算(Client/server computing)
客户和服务器系统的宗旨是在同一的资源库存储信息(通常是数据库),然后按需求分发给需要的人或机器。客户/服务概念的关键是集中存放信息,从而方便更改信息以及改动发送给信息的使用者。
信息仓库,分发信息的软件和信息与软件所在的机器统称为服务器(server);客户机器上的软件,与服务端交互,读取并处理信息然后在客户机器上展示的是客户(client)
在客户和服务器交互的过程中,为了将可能出现的问题降到最低,程序员要尽可能地分摊处理任务给客户机但是有时也给服务端处的其他机器,使用中间件(middleware)(中间件也用来提高对系统的维护)
网友评论