1、前言 I 前言 OOP(Object-Oriented Programming)早已不是一个新概念了,OOP在最近的 20 多 年里发展得异常迅猛,特别是最近的 10多年里,OOP相关技术层出不穷,当大家 热衷于使用这些新技术时,却不会使用 OOP进行软件设计,新的技术并没有为大家 带来任何好处。 很多老的开发人员从过程式开发转向面向对象的开发过程中,由于他们习惯过程式 思维的开发,尽管他们使用的是 OOP语言,但这并没有给他们带来太多帮助,反而 使他们更加厌倦 OOP的软件开发,认为 OOP没有想象中的那么便捷,很多地方没 有使用过程式开发来的便捷,于是他们又退化为过程式的开发。 越来越多的
2、新开发人员也加入了 OOP的潮流,他们追求新的技术,学会使用各种工 具和框架,却无暇顾及 OOP进行开发设计的核心。虽然使用了新技术,代码的质量 并未提高,反而事与愿违。 当他们沉浸在新技术的使用和业务逻辑的编码实现时,未料到这些拙劣的设计导致 了他们的代码不易阅读,不易维护,不易扩展,不易测试,不易调试。大家忙 忙碌碌,但是项目进度缓慢,最终往往以失败而告终。归根结底,尽管使用了 OOP 语言开发和新的技术,但对 OOP只限于粗浅的了解和相关语言语法使用上的理解, 他们并不会真正使用 OOP进行开发设计,以致使用时颠三倒四,未能真正享受到 OOP和这些新技术带来的好处,有些新技术非但没有提供
3、帮助,反而成为某些软件 失败的罪魁祸首。那么,如何使用 OOP进行开发设计呢? OOP开发新手由于没有这方面的设计经验, 在遇到问题时, 往往诉求于逻辑的实现, 在维护性和扩展性没有考虑或者少有考虑,导致代码却乱七八糟,七零八散,随着 开发的深入,最终在用户各种各样的需求面前无以应对。而有经验的 OOP开发人员 会灵活使用各种模式作出优秀的设计,编写的代码健壮性高,易于阅读、维护和扩 展,可伸缩性强,开发成本也十分低廉。如果重用他们的开发经验,那么你就不需 要在相同问题上重蹈覆辙,也能设计出优秀的软件。 前言 II 市面上介绍设计模式的书籍非常多,它们一般仅仅给出 GoF 的 23个最基本的设
4、计 模式的定义和一些简单的示例,大多数读者凭此充其量只能了解它们,在使用上大 打折扣。本书精心筛选了一些我们经常在开发设计过程中使用到的模式,使用 OOP 的眼光分析它们,适时结合一些流行 J2EE框架和技术,并从横向和纵向两方面扩展 读者的思维,使读者对这些常用的模式有一个全面深刻的认识,也希望能够为正在 使用这些框架和技术的读者带来帮助。 本书内容 开发人员之间交流最快莫过于代码了,本书给出了大量代码片段,在一些重要的地 方使用黑体加粗的字体,并作了详细解释,希望能够抛砖引玉,帮助读者能够制作 出更加出色的代码。另外,本书还添加了很多图片,希望图文并茂,使这本书更加 容易阅读。 本书主要分
5、为五篇: 第一篇:模式介绍 第一章讲述了面向对象与模式之间的关系和模式的简史;随后在第二章介绍了 第一个简单的模式,模板方法模式,在这章,我们分析了代码重复所导致的“腐 臭气味” ,重复的代码是代码“臭味”中最糟糕的,在以后章节我们将会介绍各 种模式来避免代码重复。 第二篇:创建对象 使用 OOP语言的语法创建一个对象并不复杂,例如在 Java语言中使用 new 即 可。但是随着系统变得越来越复杂,使用 new直接创建对象会给系统造成很高 的耦合度。使用创建模式可以封装对象实例化的过程,把使用对象的功能和实 例化对象解耦开来,从而降低了耦合度。 在本篇最后,讨论了现在最流行的两个概念, IoC
6、和 DI,这二者是目前流行的轻 量级容器的基础。 第三篇:构建复杂结构 有时候,创建新的,更强功能的类并不需要重新编写代码,装配已有的类和对 象要来地更加快捷,也更加灵活。该篇讨论了一些常用的组装对象的模式,你 将发现,构建大的,功能更强的对象,不是只有多层继承才能实现,组合往往前言 III 是最有效的方式,本篇你将会看到如何使用继承和组合创建复杂的大结构。 第四篇:行为模式 我们在程序中经常需要封装一些对象的行为或者对象之间的通信,这篇将会讲 述三个常用的行为模式:策略模式,状态模式和 观察者模式,加上第二章介绍 的 模板方法模式,本书将一共讲述这四个常用的行为模式。 第五篇:终点还是起点
7、其实,在很早之前,就有人对面向对象思想的局限性就行了研究,提出了一种 新的编程方法AOP,第 15 章将会介绍AOP的概念及其实现技术。 尽管在前面篇章,我们学习了一些常用的模式和一些OOP设计的原则,并且了 解了AOP方面的一些知识,但这些并不能表示我们已具备解决复杂领域问题的 能力, 在 第 16 章我们讨论了在实际开发过程中如何使用面向对象进行开发设计 以及应该注意的一些问题。 在本篇末尾,第 17 章,将回顾本书的内容,重新认识OOP设计范式。 本书读者 本书不是一本面向对象和 Java语言的入门书籍, 阅读对象主要是从事 Java语言的软 件开发人员。 希望读者了解这些 Java基本
8、技术:反射,内部类,序列化,线程,动态代理,垃圾 回收,类加载器以及 Java对象的引用类型等等。 本书中会使用到 UML的一些图示,主要包括静态类图和时序图等。 希望读者了解或者使用过这些框架和技术: JDBC, Hibernate, Jpa, Spring, Ejb, Pico Container,Guice,XWork,Webwork,Struts和 EasyMock等等。由于在实际的J2EE 开发过程中,我们经常使用到这些框架,笔者将在讲述过程中适时结合这些框架与 技术,并会比较详细地介绍它们,用以消除因不理解这些技术而造成对模式理解上前言 IV 产生问题。如果读者还想做进一步的了解,
9、可以参看附录A我给大家的相关推荐书 籍和网站。 本书代码 要获取本书的示例代码,请登录 http:/ 击 进入Downloads标签页,选择最新的版本下载。也可以安装svn客户端下载文件最新 文件,svn地址为:http:/rambling-on- 让读者选择学习自己喜欢的章节并分别单独运行这些示例,作者尽量为每一种模式 提供了相对独立和完整的代码。 注意,这些示例代码的运行环境是Java 2 Platform Standard Edition SDK V6.0及以 上版本,代码使用了一些Java 5及以上的新语法和JDK的最新API,如果读者对它们 还不熟悉,查阅附录A的有关推荐书籍。 反馈
10、 尽管笔者尽最大努力去避免正文和代码中出现的任何错误,但是人无完人,难免有 纰漏错误之处。如果读者在阅读过程中发现拼写错误,代码错误,以及内容有混淆 之处,希望能够及时反馈,或许它们能够节省其他读者很多宝贵时间,也有利于完 善本书,反馈邮箱是:。 写后感 为了完成此书,笔者增删了 5 次,尽管艰辛,但在写书的过程中也发现了不少乐趣, 希望为读者在阅读过程中带来乐趣。 版权 V 版权 本书目前是一本网络书籍,还没有打印版本,本书版权和示例代码版权是不相同的, 代码版权遵守 Apache License2.0,而本书版权如下: 1. 任何人均可以在计算机,掌上设备,或其他电子设备上阅读该书。 2.
11、 目前由于该书未出打印版本,任何人不得打印该书并以纸质形式进行传播。 3. 未经笔者的许可,任何人均不可以出版发行该书的纸版本。 4. 任何人都可以在 BBS,blog,或其他媒介上引用该书,但引用时必须注明出处。目录 VI 目录 前言 I 本书内容. II 本书读者III 本书代码IV 反馈IV 写后感IV 版权. V 目录. VI 第一篇 模式介绍 .1 第 1 章 谈面向对象和模式 .2 1.1 什么是对象 .2 1.2 面向对象的模块化 .3 1.3 面向对象的好处 .3 1.4 重用 .4 1.5 模式简史 .5 1.6 什么是模式 .6 第 2 章 第一个模式 .9 2.1 从回家
12、过年说起 .9 2.2 DRY(Dont Repeat Yourself)10 2.3 模板方法(Template Method)模式 .13 2.4 引入回调(Callback) .17 2.5 总结 .20 第二篇 创建对象 .21 第 3 章 单例(Singleton)模式22 3.1 概述 .22 3.2 最简单的单例 .22 3.3 进阶 .23 3.4 总结 .28 第 4 章 工厂方法(Factory Method)模式29 4.1 概述 .29 4.2 工厂方法模式 .29 4.3 静态工厂方法 .34 第 5 章 原型(Prototype)模式36 5.1 概述 .36 5.
13、2 原型模式 .36 5.3 寄个快递 .37 5.4 实现 .37 5.5 深拷贝(Deep Copy) .40 5.6 总结 .43 第 6 章 控制反转(IoC) .44 6.1 从创建对象谈起 .44 目录 VII 6.2 使用工厂方法模式的问题 .46 6.3 Inversion of Control(控制反转,IoC).46 6.4 总结 .63 第三篇 构建复杂结构 .64 第 7 章 装饰器(Decorator)模式 .65 7.1 概述 .65 7.2 记录历史修改 .65 7.3 Open-Closed Principle(开放封闭原则,OCP) .67 7.4 装饰器(D
14、ecorator)模式 .69 7.5 总结 .76 7.6 我们学到了什么 .77 第 8 章 代理(Proxy)模式78 8.1 概述 .78 8.2 代理(Proxy)模式78 8.3 J2SE动态代理.84 8.4 代理(Proxy)模式与装饰器(Decorator)模式 91 8.5 总结 .92 第 9 章 适配器(Adapter)模式 93 9.1 概述 .93 9.2 打桩 .93 9.3 其他适配器模式 .95 9.4 测试 .97 9.5 适配器(Adapter)模式与代理(Proxy)模式 .99 第 10 章 外观(Facade)模式100 10.1 概述 .100 1
15、0.2 外观(Facade)模式100 10.3 Least Knowledge Principle(最少知识原则) 101 10.4 懒惰的老板请客 .101 10.5 EJB里的外观模式.103 10.6 总结 .105 第 11 章 组合(Composite)模式106 11.1 概述 .106 11.2 组合模式 .106 11.3 透明的组合模式 . 112 11.4 安全的组合模式VS透明的组合模式 114 11.5 还需要注意什么 . 114 第四篇 行为模式 . 115 第 12 章 策略(Strategy)模式 116 12.1 既要坐飞机又要坐大巴 . 116 12.2 封
16、装变化 . 116 12.3 策略模式 . 119 12.4 还需要继承吗 .120 12.5 总结 .122 第 13 章 状态(State)模式 .124 目录 VIII 13.1 电子颜料板 .124 13.2 switch-case实现.124 13.3 如何封装变化 .125 13.4 状态模式 .128 13.5 使用enum类型 129 13.6 与策略(Strategy)模式的比较 133 第 14 章 观察者(Observer)模式.134 14.1 股票价格变了多少 .134 14.2 观察者模式 .134 14.3 总结 .144 第五篇 终点还是起点 .146 第 15
17、 章 面向切面的编程(AOP) .147 15.1 简介 .147 15.2 记录时间 .147 15.3 AOP(Aspect-Oriented Programming) 150 15.4 AOP框架介绍 .172 15.5 AOP 联盟(AOP Alliance) .173 15.6 使用AOP编程的风险 .173 15.7 OOP还是AOP .174 15.8 总结 .174 第 16 章 面向对象开发 .176 16.1 概述 .176 16.2 写在面向对象设计之前 .176 16.3 汲取知识 .177 16.4 横看成岭侧成峰 .178 16.5 提炼模型 .180 16.6 应
18、用设计模式 .183 16.7 不能脱离实现技术 .184 16.8 重构 .185 16.9 过度的开发(Over-engineering) .186 16.10 总结.187 第 17 章 结语.188 17.1 概述 .188 17.2 面向对象的开发范式 .188 17.3 一些原则 .189 17.4 写在模式之后 .190 第六篇 附录 .192 A. 本书推荐193 Java语言相关学习的书籍.193 J2EE技术相关书籍 .194 面向对象设计相关书籍194 给Agile(敏捷)开发人员推荐的书籍 .195 网站和论坛196 B. 本书参考197 1 第一篇 模式介绍 模式被引
19、入软件开发和 OOP语言的流行是分不开的,在 80年代末至 90年代初,面 向对象软件设计逐渐成熟,被计算机界广泛理解和接受,然而专业开发人员和非专 业开发人员作出的设计差异巨大,为了让 OOP开发人员能够使用 OOP设计进行专 业开发,经过多年的不懈努力,最终由 GoF四人根据当时的一些成熟经验和解决方 案归纳出了 23条最基本的设计模式,以供 OOP开发人员学习和使用。 该篇首先从面向对象谈起,回顾一下面向对象的一些基础概念,讲述OOP开发设计 带来的好处,以及为什么要学习和使用设计模式。接着在第二章将为读者讲述第一 个最简单也最容易理解的模式模板方法(Template Method)模式
20、。 2 第1 章 谈面向对象和模式 1.1 什么是对象 也许你已经使用面向对象做开发好几年了,面向对象的概念也似乎不难理解,但是 很多人并未认识面向对象编程的本质,继续偏向于使用 PP/FP(Procedural Programming/ Functional Programming)方式编程。在讲述模式之前,我们先回顾一 下什么是面向对象。 在 OOP世界里,任何事物,不管是无形的,还是有形的,都是对象。对象是包含一 些行为和属性的一种组合体,它反映的是客观世界的任何事物。比方说,马有腿, 耳朵和嘴巴等属性,它们会跑,也会嘶叫,这些是它们的行为。每个对象都归属于 某一特定的类型,比如说,一匹
21、汗血宝马的类型是马。 面向对象的语言一般都有三个基本特征: 封装 封装是面向对象最重要的特征之一,封装就是指隐藏。 对象隐藏了数据(例如 Java语言里 private属性) ,避免了其他对象可以直接使 用对象属性而造成程序之间的过度依赖,也可以阻止其他对象随意修改对象内 部数据而引起对象状态的不一致。 对象隐藏了实现细节: 使用者只能使用公有的方法而不能使用那些受保护的或者私有的方法,你 可以随意修改这些非公有的方法而不会影响使用者; 可以隐藏具体类型,使用者不必知道对象真正的类型就可以使用它们(依 赖于接口和抽象带来的好处) 。 使用者不需要知道与被使用者有关和使用者无关的那些对象,减少了
22、耦合。 只能通过公用接口和方法使用它们。这样,由于客户程序就不能使用那些受保 护的方法(例如 Java语言里的 private方法和 protected方法) ,而你可以随意修3 改这些方法,并不会影响使用者,从而降低了耦合度。 继承 继承可以使不同类的对象具有相同的行为:为了使用其他类的方法,我们没有 必要重新编写这些旧方法,只要这个类(子类)继承包含那些方法的类(父类) 即可。从下往上看,继承可以重用父类的功能;从上往下看,继承可以扩展父 类的功能。 多态 多态可以使我们以相同的方式处理不同类型的对象:我们可以使用同一段代码 处理不同类型的对象,只要它们继承/实现了相同的类型。这样,我们没
23、有必要 为每一种类型的对象撰写相同的逻辑,极大地提高了代码重用程度。 1.2 面向对象的模块化 1.3 面向对象的好处 面向对象有很多优势,我们在这里总结了以下内容: 对象易于理解和抽象,例如马是一个类,一匹马是一个对象,跑是马的行为。 正是由于这个特性,我们很容易把客观世界反映到计算机里,极大地方便了编 程设计。 对象的粒度更大,模块化程度也更高:与方法(函数)和结构体相比,对象是 一组方法和数据的单元,所以粒度更大,这样更方便控制和使用;而模块化程 度越高,也越容易抽象。 更加容易重用代码:只要使用继承,就可以拥有父类的方法;只要创建这个对 象,就可以使用它们的公有属性和方法;只要使用多态
24、,就可以使用相同的逻 辑处理不同类型的对象。 具有可扩充性和开放性:OOP天生就具有扩展性和开放性。 代码易于阅读:阅读人员在阅读代码过程中,可以不去关注那些具体实现类, 只要关注接口的约定即可,这样更容易侧重重点。 易于测试和调试:由于代码易于阅读,也方便测试;并且,由于模块化和抽象 化程度高,越容易发现问题出在哪个模块,也就易于跟踪和调试;最后,测试 过程中,有些对象只有在软件交付运行时才能使用(例如一个发送彩信的服务 对象) ,由于对象可依赖于抽象/接口,我们在运行测试时可以使用假对象(Mock4 对象)替换这些依赖的对象,使之不影响被测试的对象,这样便减少了测试的 依赖程度。 代码容易
25、维护:基于以上各种好处,不难想象代码会变得更加容易维护。 1.4 重用 回顾软件开发的历程,我们经历了从最初的二进制编程,然后到汇编语言的编程, 最后到高级语言编程的过程,在这个过程中,我们不断地提高了语言指令的模块化: 汇编语言模块化了机器指令,一条汇编指令可能是多条机器指令的组合;高级语言 进一步模块化了汇编语言(例如 for循环等) 。这样,使用高级语言编程的过程,其 实就是重用这些大结构的模块指令的过程,效率得到大大提升。它们解决了语言到 机器的映射问题,却没有针对我们要解决的问题域(Problem domain/Problem space) 思考问题,于是对问题域进行建模开始发展。
26、我们使用结构体和方法来建立模型,这些结构体粒度小,抽象程度不高,可重用程 度也不高。随后出现了面向对象的编程,对象是方法和结构体的集合,抽象程度更 高,我们可以通过重用对象功能协作解决更复杂的问题。随着计算机的发展,一个 系统的代码也由原来的几百行增加至现在的动辄上百万行,如果从头到尾编写这些 代码,那不仅是软件开发人员的噩梦,而是整个软件行业乃至所有使用软件的行业 的噩梦。于是,重用从方法、结构体、类的重用,上升到软件的重用。 然而要使用OOP来设计可重用的软件并不容易,尽管面向对象的编程有如此之多的 好处,但仍然有很多人倾向于使用PP/FP(Procedural Programming/
27、Functional Programming)进行编程,他们列举很多使用OOP的反面例子,例如最常见的一个是 关于使用switch语句的例子,他们认为使用switch语句非常直观和简洁,而如果使用 OOP的 状态模式(State Pattern)替换,便会产生了了大量文件和代码。其实这个驳 斥只是溢于表面,没看到状态模式带来的易阅读易扩展等好处。另外,他们也喜欢 列举继承所带来的坏处来证明使用OOP进行开发还不如使用PP/FP,其 实 在 OOP开发 过程中,我们更愿意使用合成而非继承(Favor Composition over Inheritance) ,这些 不足正是我们在OOP编程过程
28、中应该避免的。 从种种问题来看,其原因归根结底是由于很多人理解了一些面向对象的基本概念和 OOP相关语言的语法,却还没领会和掌握面向对象的开发设计方法。在开发设计过5 程中,往往求助于以前使用的过程式开发设计和简单的面向对象的特征(例如继承 性) ,因而并未享受面向对象编程所带来的模块化的好处,粗粒度的好处,封装的好 处,易于抽象的好处,代码重用的好处。 如何使用面向对象的编程方法进行软件开发便成了该问题的关键。其实,有经验的 OOP开发人员并不会从头解决问题,他们往往会使用之前的成熟的解决方案来解决 类似的问题。如果能够重用他们的经验和思想开发设计软件,那就好比站在前人的 肩膀上解决问题,模
29、式能够让我们从思想上重用有经验的开发人员的解决方案来解 决问题。 我们可以看到,对于软件编程,重用非常重要。经过过去几十年的发展,重用包括 指令集的重用,方法的重用,代码的重用,服务的重用,软件的重用,设计的重用, 思想的重用等等,重用是指一切知识信息的重用。要实现重用并不那么简单,软件 发展至今,开发人员一直在重用上摸索着前进,终于迈出了一步。笔者从开始学习 编码至今,一直致力于把重用应用于软件开发,本书将会和读者一起探讨重用和如 何编写可重用的软件。 1.5 模式简史 现在,模式被广泛地运用到软件设计和其他各个领域中,然而,模式(Pattern)这 一词却最先在建筑学里被建筑师Christ
30、opher Alexander引入。在上个世纪 70年代, Christopher Alexander等人写了一系列书籍描述建筑学上的模式,其中一本名为A Pattern Language: Towns,Buildings,Construction 1 的书中讲述了建筑领域的 253 个模式,并为模式的作出了定义,指出这些模式并不会随着时间消逝而褪色。 到 1987年,Kent Beck 和Ward Cunningham开始尝试把模式设计引入编程世界,经 过几年坚持不懈的努力和尝试, 到了 1994年, 由 Erich Gamma, Richard Helm, Ralph Johnson an
31、d John Vlissides四人编写的书籍Design Patterns: Elements of Reusable Object-Oriented Software面世,这本书在后来设计模式学习和研究中影响非常广 泛,是一本经典的设计模式参考书籍。在此书中,他们总结了多年来软件开发人员 的实践经验和研究成果,收编了 23个最常用的设计模式。时至今日,这 23个设计1 参见 Christopher Alexander, Sara Ishikawa and Murray Silverstein编著的A Pattern Language: Towns, Buildings, Construct
32、ion一书,出版社为 Oxford University Press,出版时间为 1977。他们为建筑学上的模 式出版了一系列图书,这本书籍是其中的卷二,还有一本大家非常熟悉的书籍The Timeless Way of Building ,是卷一。 6 模式仍然是最基本,最经典的模式,而这四个人往往被称为“Gang of Four(四人帮, 其实是一种开玩笑的戏称) ” ,简称为“GoF” 。 由于上世纪 80年代,面向对象技术蓬勃发展, GoF提出的这些模式都来自于面向对 象开发设计的经验,即这些模式都是有关面向对象开发设计的。由于面向对象的强 大生命力,随着越来越多的人加入面向对象的大军,
33、面向对象的设计模式也得到极 大地推广和发展,涌现出了很多出色的新模式。 1.6 什么是模式 我们刚才简要地介绍了模式的发展历史,但是,什么是模式呢?建筑师 Christopher Alexander给出这样的定义: 这个定义较长,一般情况下,模式被简单地定义为如下: 是的,模式就是一个解决方案,一个模式解决了一类特定的问题,当我们再次遇到 同样的问题时,我们仍然可以使用它解决同样的问题。 这个定义很容易方便初学者认识什么是模式,但是很多人对此定义提出了异议,在 Head First Design Patterns 2 一书第 13章中,作者就列举了一个例子驳斥了该概 念: 2参见 Eric T
34、 Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra等人编写的Head First Design Patterns一 书,出版社为 OReilly Media,,出版日期为 October 2004。 A pattern is a solution to a problem in a context. 模式是某一上下文环境中一个问题的解决方案。 Each pattern describes a problem which occurs over and over again in our environment, and then descr
35、ibes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice. 每一个模式都描述了一个在我们周围不断发生的问题,以及该问题的解决方案的核心, 这样,你就能够无数次地使用该解决方案而不用按照同样的方式重做一遍。 7 是的,不是任何一个解决方案都能成为模式。在 Christopher Alexander给出的定义 里,就明确指出了,如果一个解决方案称得上
36、是模式,那它要能够被使用无数次, 经得起时间的考验。GoF为模式给出的定义如下所示: 可以看出,GoF给出的定义更加侧重于 OOP编程设计。其实,我更愿意使用如下定 义: 模式是一个常用的解决方案(general solution) ,而非仅仅是一个 solution,显然, 打碎车窗玻璃并不能被看作是一个可以经常使用的解决方案。这个定义可以帮助读 者简单但不失深刻地理解模式含义。 为了方便这 23个经典模式的交流和传播, GoF在书中为每个模式定义了 4 个基本要 素,即模式名称(Pattern name) 、问题(Problem) 、解决方案(Solution)和效果 (Consequen
37、ce) ,全面地描述了在特定的上下文,相关的类和对象如何协作来解决 这些常见的问题。而本书并不会从这些要素入手去重复 GoF的工作,而是通过一些 场景,讨论我们如何使用这些模式,并尽力纵横扩展我们的视野,希望以此启发读 A pattern is a general solution to a problem in a context. 模式是某一上下文环境中一个问题的常用解决方案。 The design patterns are descriptions of communicating objects and classes that are customized to solve a ge
38、neral design problem in a particular context. 设计模式描述了在一个特定上下文里,如何定制这些互相通信的对象和类来解决一 个常见设计问题。 While an absent-minded person might lock his keys in the car often, breaking the car window doesnt qualify as a solution that can be applied over and over (or at least isnt likely to if we balance the goal wi
39、th another constraint: cost). 一个健忘的人或许经常会把钥匙锁在汽车里,打破汽车的窗玻璃不是一个能够经常使用 的解决方案(至少如果我们权衡另外一个限制条件花费时,也极不可能使用) 。 8 者,为 OOP开发设计打下坚实的基础。读者在阅读本书期间,可以参阅此书以作全 面了解。 9 第2 章 第一个模式 模板方法(Template Method)模式 2.1 从回家过年说起 春节是中国传统节日里最热闹的,对在外漂泊多时的游子而言,最幸福的事莫过于 是回家过年。为了回家庆祝团圆,我们首先需要购买火车票,然后乘坐火车,最后 才能和家人团聚。 我们这里编写一个简单的程序来模拟
40、这个过程,最初的设计非常简单,我们编写了 一个 HappyPeople类,它有一个 celebrateSpringFestival()的方法,我们把买票,回家 和家里庆祝的逻辑代码都写在这个方法里,代码大致如下所示: public class HappyPeople public void celebrateSpringFestival() / /B u y ing tic ket System.out.println(“Buying ticket.“); /Travelling by train System.out.println(“Travelling by train“); /Cele
41、brating Chinese New Year System.out.println(“Happy Chinese New Year!“); 后来,我们逐渐发现,有人需要坐火车回家,有人需要坐飞机回家,而有人坐大巴 回家就可以了。但是不管你乘坐哪种交通工具回家,都得先买票,然后才能和家人 团聚。 于是我们又创建了一个新类 PassengerByCoach来实现坐汽车回家过年的逻辑, 由于买票和在家庆祝的代码一样,我们把类 HappyPeople的代码快速复制了一份, 把乘坐交通工具的那部分代码替换成了坐大巴的逻辑,结果如下: public class PassengerByCoach pub
42、lic void celebrateSpringFestival() 10 / /B u y ing a ti ck et System.out.println(“Buying ticket“); /Travelling by train System.out.println(“Travelling by train“); /Celebrating Chinese New Year System.out.println(“Happy Chinese New Year!“); 斜体加粗的部分便是我们替换的那部分代码,接着,我们同样使用复制&粘贴 (Copy&Paste)方式编写了类 Passen
43、gerByAir来实现表示坐飞机回家的那类人的需 求。 复制&粘贴看起来非常实用,但是没过几周,我们慢慢发现情况不妙,这几个类的 代码开始变得难以维护:如果买票逻辑有所改变,我们需要分别修改这三个类,但 有的时候,马虎的工程师不会在所有类上做相应的修改;而且,由于这些类的功能 发生了变化,相应类的测试代码也要做改变,这样修改这些类的测试代码和修改这 些类一样,出现了相同的重复修改的问题;最后,我们开始变得越来越担心:因为 随着交通工具的增多,势必我们需要开发更多类和测试类,这样维护就变得越来越 麻烦。 最终我们停下来开始思考:复制&粘贴真的很好用吗?该不该编写重复的代码呢? 让我们首先从 DR
44、Y(Dont Repeat Yourself)原则谈起吧。 2.2 DRY(Dont Repeat Yourself) 2.2.1 写在DRY之前 2.2.1.1 变化 在这个纷繁的世界上,你能否找到一样东西,它会永久不变? 11 谚语: 不管是日月星辰,还是软件开发,变化是永恒的。在软件开发过程中,一切都在变 化: 需求变化:这也是软件开发中最主要的变化,我们经常听到的各种各样的抱怨, 例如,之前拟定的需求突然又变化了;客户之前并不知道他们想要什么,现在 还不能确定这些就是他们想要的;以前的需求根本是错误的;之前的需求并没 有列出所有情形,这都直接影响着软件开发设计。 技术变化:新技术不停地
45、涌现,旧的技术被逐渐淘汰。 团队结构的变化:团队成员并不稳定,有来的,也有去的;此外团队组织结构 的也在发生变化,主要体现在管理者的变化。 政治因素的变化:公司处于各种利益的考量,采取的一些行为干涉软件开发, 这些行为往往是不可预料和没有回旋余地的。虽然这些行径经常被开发人员嗤 之以鼻,但是这些变化真真实实地影响了软件的开发。 其他因素变化:自然灾害,航空灾难等等都可能影响软件开发。 总之,软件开发的最大的特征就是变化,变化总是在发生的,我们不能因为害怕变 化而逃避它,否定它。谁能从容应对软件开发中的变化,谁就能最大程度上降低软 件开发中的风险,成为这一方面的佼佼者。 2.2.1.2 开发还是
46、维护 Andy Hunt和Dave Thomas 在The Pragmatic Programmer一书中认为,软件开发 人员始终处于软件维护过程中,我个人非常认同这个观点,引用此书中的原话为 3 : 3参见The Pragmatic Programmer一书,第二章:A Pragmatic Approach。 The Only Thing In The World That Doesnt Change Is Change Itself. 世界上唯独不变的是变化本身。 12 没错,不管是在应用发布之前还是之后,我们要么在修改程序错误(我们称为 Bug) , 要么为增强软件功能(我们称为 Enh
47、ancement)而修改代码,这些都属于软件维护 的范畴。总而言之,软件维护始终贯穿于软件开发的始末。 2.2.2 DRY(Dont Repeat Yourself) DRY(Dont Repeat Yourself,不要复制自己) :也叫DIE(Duplication Is Evil, 即复制是魔鬼) ,这个原则在Andy Hunt和Dave Thomas所著的The Pragmatic Programmer一书中阐述为 4 : OAOO(Once and Only Once,仅此一次) :OAOO指的是要避免的代码重复, 代码应该简洁,如果你发现代码“臭味”时,重复的代码是其中最严重的。
48、从以上定义我们不难发现, DRY原则所涉及的范围其实要比 OAOO宽泛的多, DRY 涉及的范围不仅包括代码,任何知识都算,例如逻辑,常量,标准,功能,服务等 等,而 OAOO指的是不能编写重复的代码,我们在本书主要将着力讨论如何使用设 计模式避免代码的重复。 2.2.3 变化+重复,如何维护 我们已经知道,在软件开发过程中,变化时刻发生着,特别是需求变化,如果像前4参见The Pragmatic Programmer一书,第二章:A Pragmatic Approach。 Every piece of knowledge must have a single, unambiguous, au
49、thoritative representation within a system. 每份知识在一个系统中必须存在唯一的,明确的,权威的表述。 Programmers are constantly in maintenance mode. Our understanding changes day by day. New requirements arrive as were designing or coding. Perhaps the environment changes. Whatever the reason, maintenance is not a discrete activity, but a routine part of the entire development process. 程序员始