Thinking in java 第一章 - 对象导论

第一章 - 对象导论

我们之所以将自然界分解,组织成各种概念,并按其含义分类,只要是因为我们是整个口与交流社会共同遵守的协定的参与者,这个协定以语言的形式固定下来…除非赞成这个协定中规定的有关语言信息的组织和分类,否则我们根本没法交谈。
                                                                                                                                                                                        ——Benjamin Lee Whorf (1897 ~ 1941)

1 抽象过程

所有的编程语言都提供抽象机制。可以认为,人们所能够解决的问题的复杂性直接取决于抽象的类型和质量。

面向对象方式通过向程序员提供表示问题空间中的元素的工具而更近了一步。
我们将问题空间中的元素及其在解空间中的表示称为“对象”。(你还需要一些无法类比为问题空间元素的对象。)这种思想的实质是:程序可以通过添加新类型的对象使自身适用于某个特定问题。

OOP允许根据问题来描述问题,而不是根据运行解决方案的计算机来描述问题。

纯粹的面向对象程序设计方式特性
  1. 万物皆为对象
  2. 程序是对象的集合,它们通过发送消息来告知彼此所要做的。
  3. 每个对象都有自己的由其他对象所构成的存储。
  4. 每个对象都拥有其类型。
  5. 某一特定类型的所有对象都可以接收同样的消息。

对象具有状态、行为和标识。这意味着一个对象都可以拥有内部数据(它们给出了该对象的状态)和方法(他们产生的行为),并且每一个对象都可以唯一地与其他对象区分开来,具体来说,就是每一个对象在内存中都有一个唯一的地址。

2 每个对象都有一个接口

每个对象都只能满足某些请求,这些请求由对象的接口(interface)所定义,决定接口的便是类型。

接口确定了对某以特定对象所能发出的请求。但是,在程序中必须有满足这些请求的代码。这些代码与隐藏的数据一起构成了实现。

向对象发送请求时,此过程通常被概括为:向某个对象“发送消息”(产生请求),这个对象便知道西消息的目的,然后执行对应的程序代码。

3 每个对象都提供服务

将对象想象为“服务提供者”。程序本身将向用户提供服务,它将调用其他对象提供的服务来实现这一目的。
将对象看作是服务提供者还有一个附带的好处:它有助于提高对象的内聚性。是软件设计的基本质量要求之一:这意味着一个软件构件(例如一个对象,当然它也有可能是指一个方法或者一个对象库)的各个方面“组合”得很好。

将对象作为服务提供者看待是一件伟大的简化工具,这不仅在设计过程中非常有用,而且当其他人试图理解你的代码或者重用摸个对象时,如果他们看出了这个对象所能提供的服务的价值,它会使调整对象以适应其设计的过程变得简单得多。

4 被隐藏的具体实现

将程序开发人员按照角色分为类创建者(那些创建新数据类型的程序员)和客户端程序员(那些在其应用中使用数据类型的类消费者)是大有裨益的。

访问控制的存在原因
  1. 让客户端程序员无法触及他们不应该触及的部分——这些部分对数据类型的内部操作来说是必需的,但并不是用户解决特定问题所需的接口的一部分。这对客户端程序员来说其实是一项服务,因为他们可以容易地看出哪些东西对他们来说很重要,而哪些东西可以忽略。
  2. 允许库设计者可以改变类内部的工作方式而不用担心会影响到客户端程序员。

Java有一种默认的访问权限,当没有使用前面提到的任何访问指定词时,它将发挥作用。这种权限通常被称为包访问权限,因为在这种权限下,类可以访问同一个包(类库件)中的其他类的成员,但是在包之外,这些成员如同指定了private一样。

5 复用具体实现

代码复用是面向对象程序设计语言所提供的最了不起的优点之一。

组合: 新的类可以由任何数量、任何类型的其他对象以任意可以实现新的类中想要的功能的方式所组成。(使用现有的类合成新的类)如果组合是动态发生的,那么它通常被称为聚合。组合经常被视为“has-a”(拥有)关系。

组合带来了极大的灵活性。新类的成员对象通常被声明为private,使得使用新类的客户端程序员不能访问它们。
在实际开发时,在建立新类时,应当首先考虑组合,因为它更加简单灵活。如果采用这种方式,设计会变得更加清晰。

6 继承

导出类与基类具有相同的类型。
当继承现有类型时,也就创造了新的类型。这个新的类型不仅包括现有类型的所有成员(尽管private成员被隐藏了起来,并且不可访问)而且更重要的是它复制了基类的接口。一个圆形也就是一个几何形。

有两种方法可以使基类与导出类产生差异。

  1. 直接在导出类中添加新方法。
  2. 改变现有基类方法的行为,这称为覆盖那个方法。
6.1 “是一个” 与 “像是一个” 关系

——is-a(是一个)关系
如果继承只覆盖基类的方法(而并不添加在基类中没有的新方法),就意味着导出类和基类是完全相同的类型,因为它们具有完全相同的接口。结果可以用一个导出类对象来完全替代一个基类对象。这可以被视为纯粹替代,通常称之为替代原则。在某种意义上,这是一种处理继承的理想方式。

——is-like-a(显示一个)关系
又是必须在导出类型中添加新的接口元素,这样也就扩展了接口。这个新的类型仍然可以替代基类,但是这种代替并不完美,因为基类无法访问新添加的方法。新类型具有旧类型的接口,但是它还包含其他方法。所以不能说它们完全相同。

7 伴随多态的可互换对象

编译器不可能产生传统意义上的函数调用。一个非面向对象编程的编译器产生的函数调用会引起所谓的前期绑定。这么做意味着编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。然后在OOP中,程序直到运行时才能够确定代码的地址,所以当消息发送到一个泛化对象时,必须采用其他的机制。

后期绑定: 当向对象发送信息时,被调用的代码直到运行时才能确定。编译器确保被调用方法的存在,并对调用参数和返回值执行类型检查(无法提供此类保证的语言被称为是弱类型的),但是并不知道将被执行的确切代码。

为了执行后期绑定,Java使用一小段特殊的代码来替代绝对地址调用。这段地址使用在对象中存储的信息来计算方法体的地址。具体剖析请移步到第八章。

在某些语言中,必须明确的声明希望摸个方法具备后期绑定属性所带来的灵活性(C++是使用virtual关键字来实现的)。在这些语言中,方法默认情况下不是动态绑定的。而在Java中,动态绑定是默认行为,不需要增加而外的关键字来实现多态。

当向一个对象发送信息时,即使涉及向上转型,该对象也知道要执行什么样的正确行为。

8 单根继承结构

在单根继承结构中的所有对象都具有一个公共接口,所以它们归根到底都是相同的基本类型。(这个终极基类的名字就是Object)。

单根继承结构保证所有对象都具备某些功能。因此你知道,在你的系统中你可以在每个对象上执行某些基本操作。所有对象都可以很容易地在堆上创建,而参数传递也得到了极大的简化。

单根继承结构使垃圾回收器的实现变得容易得多,而垃圾回收期器正是Java相对C++的重要改进之一。由于所有对象都保证具有其类型信息,因此不会因无法确定对象的类型而陷入僵局。这对于系统级操作(如异常处理)显得尤为重要,并且给编程带来了更大的灵活性。

9 容器

从设计的观点来看,真正需要的只是一个可以被操作,从而解决问题的序列。如果单一类型容器可以满足所有需求,那么就没有理由设计不同种类的序列了。然而还是需要对容器有所选择,这有两个原因:

  1. 不同容器提供了不同类型的接口和外部行为。
  2. 不同的容器对于某些操作具有不同的效率。
9.1 参数化类型

在Java SE5出现之前,容器存储的对象都只具有Java中的通用类型:Object
Java SE5 的重大改变之一就是增加了参数化类型,在Java中它称为泛型。一对尖括号,中间包含类型信息,通过这些特征就可以识别对泛型的使用。

解决方案被称为参数化类型机制。
参数化类型就是一个编译器可以自动定制作用于特定类型上的类。

10 对象的创建和生命期

对象的数据位于何处?怎样控制对象的生命周期?
C++认为效率控制是最重要的议题,所以给程序员提供了选择的权力。为了追求最大的执行速度,对象的存储空间和生命周期可以在编写程序时确定,这可以通过将对象置于堆栈(它们有时被称为自动变量或限域变量)或者静态存储区域内来实现。这种方式将存储空间分配和释放置于优先考虑的位置,某些情况下这样控制非常有价值。但是,也牺牲了灵活性,因为必须在编写程序时知道对象确切的数量、生命周期和类型。如果试图解决更一般话的问题,例如计算机辅助设计、仓库管理或者空中交通控制,这种方式就显得过于受限了。

第二种方式是在被称为堆的内存池动态地创建对象。在这种方式中,直到运行时才知道需要多少对象,它们的生命周期如何,以及它们的具体类型是什么。这些问题的答案只能在程序运行时相关代码被执行的那一刻才能确定。因为存储空间是在运行时动态管理的,所以需要大量的时间在堆中分配存储空间,这可能要远远大于在堆栈中创建存储空间的时间。在堆栈中创建存储空间和释放存储空间通常各需要一条汇编指令即可,分别对应栈顶指针向下移动和栈顶指针向上移动。创建堆存储空间的时间依赖于存储机制的设计。

动态方式有这样一个一般性的逻辑假设:对象趋向于变得复杂,所以查找和释放存储空间的开销不会对对象的创建造成重大冲击。动态方式所带来的更大的灵活性正是解决一般化编程问题的要点所在。

Java完全采用了动态内存分配方式。每当想要创建新对象时,就要使用new关键字来构建此对象的动态实例。

对象生命周期
对于允许在堆栈上创建对象的语言,编译器可以确定对象存活的时间,并可以自动销毁它。然而,如果是在堆上创建对象,编译器就会对它的生命周期一无所知。像C++这样的语言中,必须通过编程方式来确定何时销毁对象,这可能会因为不能正确处理而导致内存泄漏(这在C++程序中是常见的问题)。Java提供了被称为“垃圾回收器”的机制,它可以自动发现对象何时不再被使用,并继而销毁它。

Java垃圾回收器被设计成用来处理内存释放问题(尽管它不包括清理对象的其他方面)。垃圾回收器“知道”对象何时不再被使用,并自动释放对象占用的内存。

11 异常处理:处理错误

Java的异常处理在众多的编程语言中格外引人注目,因为Java一开始就内置了异常处理,而且你必须强制使用它。它是唯一可接受的错误报告方式。如果没有编写正确的处理异常的代码,那么就会得到一条编译时的出错消息。这种有保障的一致性有时会使得错误处理非常容易。

值得注意的是,异常处理不是面向对象的特性—尽管在面向对象语言中异常常被表示成为一个对象。异常处理在面向对象语言出现之前就已经存在了。

12 并发编程

通常,线程只是一种为单一处理器分配执行时间的手段。但是如果操作系统支持多处理器,那么每个任务都可以被指派给不同的处理器,并且它们是在真正地并行执行。在语言级别上,由于程序在逻辑上被分为线程,所以如果机器拥有多个处理器,那么程序不需要特殊调整也能执行得更快。

所有这些都使得并发看起来相当简单,但是有一个隐患:共享资源。如果有多个并行任务都要访问同一项资源,那么就会出问题。例如,两个进程不能同时向一台打印机发送信息。为了解决这个问题,可以共享的资源,例如打印机,必须在使用期间被锁定。因此,整个过程是:某个任务锁定某项资源,完成其任务,然后释放资源锁,使其他任务可以使用这项资源。

Java的并发是内置于语言中的,Java SE5已经增添了大量额外的库支持。

13 Java 与 Internet

尽管Java对于解决传统的单机程序设计问题非常有用,但同样重要的是,它解决了在万维网上的程序设计问题。

13.1 Web是什么

1.客户/服务器计算技术
客户/服务器系统的核心思想是:系统具有一个中央信息处理池,用来存储某种数据,它通常存在于数据库中,你可以根据需要将它分发给某些人员或机器集群。

客户/服务器计算技术的基本概念并不复杂。问题在于你只有单一的服务器,却要同时为多个用户服务。
通常,这会涉及数据库管理系统,因此设计者把数据“均衡”分布于数据表中,以取得最优的使用效果。此外,系统通常允许客户在服务器中插入新的信息。这意味着必须保证一个客户插入的信息不会覆盖另一个客户插入的新数据,也不会在将其添加到数据库的过程中丢失(这被称为事务处理)。如果客户端软件发生变化,那么它必须被重新编译、调试并安装到客户端机器上,事实证明这比想象的要更加复杂与费力。如果想支持多种不同类型的计算机和操作系统,问题将更麻烦。最后还有一个最重要的性能问题:可能在任何时刻都有成百上千的客户向服务器发出请求,所有任何小的延迟都会产生重大影响。为了将延迟最小化,程序员努力减轻处理任务的负载,通常是分散给客户端机器处理,但有时也会使用所谓的中间件将负载分散给在服务器端的其他机器。(中间件也被用来提高可维护性。)
2.Web就是一台巨型服务器
Web浏览器从最开始只具备最基本的观察功能(观察器),到现如今具备可以运行程序的能力。
作为观察器时,虽然连最简单的计算任务都不能执行,但它却是安全的,因为它在你的本地不会执行任何程序,而这些程序有可能包含bug和病毒。

13.2 客户端编程

客户端编程意味着Web浏览器能够用来执行任何它可以完成的工作,使得返回给用户的结果更加快捷,而且使得你的网站更加具有交互性。
个人理解:就只在逐渐的将原本是服务器端的工作迁移到客户端上,减少服务器的负担,提升用户体验。

客户端编程的问题是:它与通常意义上的编程十分不同,参数几乎相同,而平台却不同。Web浏览器就像是一个功能受限的操作系统。最终,你仍然必须编写程序,而且还得处理那些令人头晕眼花的成堆的问题,并以客户端编程的方式来产生解决问题方案。
1.插件
插件开发。通过这种方式,程序员可以下载一段代码,并将其插入到浏览器的适当的位置,以此来为浏览器添加新功能。
编写插件并不是一件轻松的事情。插件对于客户端编程的价值在于:他允许专家级的程序员不需经过浏览器生产厂商的许可,就可以开发出某种语言扩展,并将它们添加到服务器中。因此,插件提供了一个“后门”,使得可以创建新的客户端编程语言(但是并不是所有的客户端编程语言都是以插件的形式实现的)。
2.脚本语言
插件引发了浏览器脚本语言的开发。通过使用某种脚本语言,你可以将客户端程序的源代码直接嵌入到HTML页面中,解释这种语言的插件在HTML页面被显示时自动激活。缺点是代码会暴露给任何人去浏览(或窃取)。

JavaScript是一种在Web浏览器不需要任何插件的情况下就可以得到支持得到脚本语言。
3.Java
Java是通过applet以及使用Java Web Start来进行客户端编程的。
4.备选方案
5.C#和.NET
6.Internet与Intranet(企业内部网)

13.3 服务器端编程

大部分时间,请求指示要求“给我发送一个文件”,之后浏览器会以某种适当的形式解释这个文件,例如将其作为HTML页面、图片、Java applet或脚本程序等来解释。
更复杂的对服务器的请求通常涉及数据库事务。例如:
1.复杂的数据库搜索请求,然后服务器将结果进行格式编排,使其成为一个HTML页面发回给客户端。
2.注册信息,加入团体,下订单时涉及到对数据库的修改。

14 总结

编写良好的Java程序通常比过程型程序要简单得多。但是,你看到的只是有关下面两部分内容的定义:

  1. 用来表示问题空间的对象(而不是有关计算机表示方式的内容)
  2. 发送给这些对象的用来表示在此空间内的行为的消息
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值