0010 Java基础之对象导论

本篇文章将介绍的是背景性和补充性的材料。许多人在没有了解面向对象程序设计的全貌之前,感觉无法轻松自在的从事此类的编程。因此,在这边引入比较多的概念,以期帮助读者扎实地了解 OOP。

一:抽象过程

所有的编程语言都提供抽象机制。可以认为,人们所能够解决的问题的复杂性直接取决于抽象的类型和质量。所谓的“类型”是指“所抽象的是什么?”汇编语言是对底层机器的轻微抽象。接着出现许多所谓的“命令式”语言(basic,c)都是对汇编语言的抽象。而面向对象方式通过向程序员提供表示问题空间中的元素的工具而更进一步,这种表示方式非常通用,使得程序员不会受限于任何特定类型的问题。我们将问题空间中的元素及其在解空间中的表示称为“对象”。一种纯粹的面向对象程序设计方式有以下几点:

(1):万物皆为对象

(2):程序是对象的集合,他们通过发送消息来告知彼此所要做的

(3):每个对象都有自己的由其他对象所构成的存储

(4):每个对象都拥有其类型

(5):某一特定类型的所有对象都可以接收同样的消息

用一句更加简洁的话描述:对象具有状态、行为和标识。这意味着每一个对象都可以唯一拥有内部数据和方法,并且每一个对象都可以唯一的与其他对象区分开来,具体说来,就是每一个对象在内存中都有一个唯一的地址。

二:每个对象都有一个接口

      尽管我们在面向对象程序设计中实际上进行的是创新的数据类型,但事实上所有的面向对象程序设计语言都使用 calss 这个关键字来表示数据类型。当看到类型一词时,可将其作为类来考虑,反之亦然。

类一旦被建立,就可以随心所欲地创建类的任意个对象,然后去操纵它们,就像它们是存在于你的待求解问题中的元素一样。事实上,面向对象程序设计的挑战之一,就是问题空间的元素和解空间的对象之间创建一对一的映射。

但是,怎么样获得有用的对象呢?必须有某种方式产生对对象的请求,使对象完成各种任务。每个对象都只能满足某些请求,这些请求由对象的接口(interface)所定义,决定接口的便是类型。以灯泡为例:



如图:

<span style="font-size:24px;">Light lt = new Light();
lt.on();</span>

类型的名称是 Light ,特定的 Light 对象的名称是 lt ,可以向 Light 对象发出的请求是:打开它、关闭它、将它调亮、将它调暗。

三:每个对象都提供服务

当正在试图开发或理解一个程序设计时,最好的方法之一就是将对象想像为“服务提供者”。程序本身将向用户提供服务,它将通过调用其他对象提供的服务来去实现这一目的。而你的目标就是去创建能够提供理想的服务来解决问题的一系列对象。将对象作为服务提供者看待是一件很伟大的简化工具,这不仅仅在设计过程中非常有用,而且当其他人试图理解你的代码或重用某个对象时,如果他们看出了这个对象所能提供的服务的价值,它会使调整对象以适应其设计的过程变得简单很多。

四:被隐藏的具体实现

将程序开发人员按角色分成类的创建者和客户端程序员是大有裨益的。客户端程序员的目标是收集各种用来实现快速应用开发的类。类创建者的目标的构建类,这种只向客户端程序员暴露必需的部分,而隐藏其他的部分。

访问控制存在的两个原因:一是让客户端程序员无法触及他们不该触及的部分;二是允许库设计者可以改变类内部的工作方式而不必担心会影响到客户端程序员。Java 用三个关键字在类的内部设定边界:public、private、protected。public 表示紧随其后的元素对任何人都是可用的,而 private 这个关键字表示除类型创建者和类型的内部方法之外的任何人都不能访问的元素,protected 关键字与 private 作用相当,差别仅在于继承的类可以访问 protected 成员,但是不能访问 private 成员。

五:继承

类型不仅仅只是描述了作用于一个对象集合上的约束条件,同时还有与其他类型之间的关系。两个类型可以有相同的特性和行为,但是其中一个类型可能比另一个含有更多的特性,并且可以处理更多的消息。继承使用基类型和导出类型的概念表示了这种类型之间的相似性。

举个例子,基类是几何形,每一个几何都具有尺寸、颜色、位置等,同时每一个几何形都可以被绘制、擦除、移动和着色等。在此基础之上,可以导出具体的几何形状--圆形、正方形、三角形等,每一种都具有额外的特性和行为,例如某些形状可以被翻转,某些形状计算面积的方式不一样等。类型层次结构同时体现了几何形状之间的相似性和差异性。如图所示:


由于基类和导出类具有相同的基础接口,所以伴随此接口的必定有某些具体实现。也就说,当对象接收到特定的消息时,必须有某些代码去执行。如果只是简单的继承一个类而并不做其他任何事,那么在基类接口中的方法将会直接继承到导出类中,这意味着导出类的对象不仅与基类拥有相同的类型,还拥有相同的行为,这样做并没有什么特别的意义。有两种方法可以使基类与导出类产生差异,第一种比较直接:直接在导出类中添加新的方法。如图所示:


第二种,也是更为重要的一种使导出类和基类之间产生差异的方法是改变现有基类的方法的行为,这被称之为覆盖。


在 OOP 中,除了 C++ ,所有的面向对象语言都继承单一的基类 Object,Java也不例外。在单根继承结构中所有对象都具有一个共用接口,所以他们归根到底都是相同的基本类型。

六:伴随多态的可互换对象

在处理类型的层次结构时,经常想把一个对象不当作它所属的特定类型来对待,而是将其当作基类的对象来对待。这使得人们可以编写出不依赖于特定类型的代码。而且添加新类型的扩展一个面向对象程序以便处理新情况的最常用方式。

如果不需要知道哪段代码会被执行,那么当添加新的子类型时,不需要更改调用它的方法,他就能够执行不同的代码。因此,编译器无法精确地了解哪一段代码将会被执行,那么该怎么办呢?这个问题的答案,也是面向对象程序设计的最重要的妙诀:编译器不可能产生传统意义上的函数调用。一个非面向对象编程的编译器产生的函数调用会引起所谓的前期绑定,这么做意味着编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。然而在 OOP 中,程序直到运行时才能确定代码的地址,所以当消息发送到一个泛化对象时,必须采用其他的机制。

     后期绑定:当向对象发送消息时,被调用的代码直到运行时才能确定,为了执行后期绑定,Java 使用一小段特殊的代码来代替绝对地址调用,这段代码使用在对象储存的信息来计算方法体的地址。这样,根据这一小段代码的内容,每一个对象都可以具有不同的行为表现。当向一个对象发送消息时,改对象就能够知道对这条消息应该做些什么。

七:容器

通常来说,如果不知道在解决某个特定问题时需要多少个对象,或者他们将存活多久,那么就不可能知道如何存储这些对象。如何才能知道需要多少空间来创建这些对象呢?答案是你不可能知道,因为这类信息只有在运行时才能获得。

幸运的是,Java 在其标准类库中有大量的容器,他们作为开发包的一部分,在某些类库中,一两个通用容器足够满足所有的需要,但是在其他类库中,具有满足不同需要的各种类型的容器,例如:List(用于存储序列),Map(用来建立对象之间的关联),Set(每种对象类型只持有一个),以及诸如队列、树、堆栈等更多的构件。

从设计的观点来看,真正需要的只是一个可以被操作,从而解决问题的序列。如果单一类型的容器可以满足所有的需要,那么久没有理由设计不同种类的序列了。然而还是需要对容器有所选择,有两个原因:一是不同容器提供了不同类型的接口和外部行为。另一个是不同的容器对于某些操作具有不同的效率,最好的例子就是:ArrayList 和 LinkList 。这边给一句小口诀就记住了:ArrayList 读快改慢,LinkList 则是读慢改快。









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值