本书是设计模式领域公认的3本经典著作之一,“极具趣味,容易理解,但讲解又极为严谨和透彻”是本书的写作风格和方法的最大特点。第1版2010年出版,畅销至今,广受好评,是该领域的里程碑著作。深刻解读6大设计原则和28种设计模式的准确定义、应用方法和最佳实践,全方位比较各种同类模式之间的异同,详细讲解将不同的模式组合使用的方法。第2版在第1版的基础上有两方面的改进,一方面结合读者的意见和建议对原有内容中的瑕疵进行了修正和完善,另一方面增加了4种新的设计模式,希望这一版能为广大程序员们奉上一场更加完美的设计模式盛宴!
全书共38章,分为五部分:第一部分(第1~6章),以一种全新的视角对面向对象程序设计的6大原则进行了深刻解读,旨在让读者能更深刻且准确地理解这些原则,为后面的学习打下基础;第二部分(第7~29章)通过大量生动的案例讲解和分析了23种最常用的设计模式,并进行了扩展讲解,通俗易懂,趣味性极强而又紧扣模式的核心;第三部分(第30~33章)对同类型和相关联的模式进行了深入分析和比较,旨在阐明各种设计模式之间的差别以及它们的理想应用场景;第四部分(第34~36章)探讨了如何在实际开发中将各种设计模式混合起来使用,以发挥设计模式的最大效用;第五部分(第37~38章)是本书的扩展篇,首先从实现的角度对MVC框架的原理进行了深入分析,然后讲解了5种新的设计模式的原理、意图和最佳实践。本书最后附有一份精美的设计模式彩图,可以裁剪,便于参考。
(1) 畅销书全新升级,第1版广受好评,被誉为设计模式领域最具趣味、最易理解且又讲解极为透彻的一本书,程序员公认的3本经典设计模式著作之一(2) 深刻解读6大设计原则和28种设计模式的准确定义、应用方法和最佳实践,全方位比较各种同类模式之间的异同,详细讲解将不同的模式组合使用的方法
秦小波 资深软件开发工程师、系统分析师和架构师(获Sun架构师认证),从事软件开发工作10余年,实践经验极其丰富。精通设计模式,对设计模式有深刻的认识和独到见解,经过长期大量的实践和总结,创造性地提出新的设计模式。资深Java技术专家,精通Spring、Struts 2、Hibernate、iBatis、jBPM等Java技术,在企业级Java应用领域积累了大量经验,对基于ESB、BPEL的服务集成技术也有深入的认识。此外,还是一位优秀的DBA,具有IBM DB2 DBA资格认证,对海量数据处理有深入的研究。著有畅销书《编写高质量代码:改善Java程序的151个建议》,广受读者好评!
"前 言
第一部分 大旗不挥,谁敢冲
锋—6大设计原则全新解读
第1章 单一职责原则 2
1.1 我是“牛”类,我可以担任多职吗 2
1.2 绝杀技,打破你的传统思维 3
1.3 我单纯,所以我快乐 6
1.4 最佳实践 7
第2章 里氏替换原则 8
2.1 爱恨纠葛的父子关系 8
2.2 纠纷不断,规则压制 9
2.3 最佳实践 18
第3章 依赖倒置原则 19
3.1 依赖倒置原则的定义 19
3.2 言而无信,你太需要契约 20 "前 言
第一部分 大旗不挥,谁敢冲
锋—6大设计原则全新解读
第1章 单一职责原则 2
1.1 我是“牛”类,我可以担任多职吗 2
1.2 绝杀技,打破你的传统思维 3
1.3 我单纯,所以我快乐 6
1.4 最佳实践 7
第2章 里氏替换原则 8
2.1 爱恨纠葛的父子关系 8
2.2 纠纷不断,规则压制 9
2.3 最佳实践 18
第3章 依赖倒置原则 19
3.1 依赖倒置原则的定义 19
3.2 言而无信,你太需要契约 20
3.3 依赖的三种写法 25
3.4 最佳实践 26
第4章 接口隔离原则 28
4.1 接口隔离原则的定义 28
4.2 美女何其多,观点各不同 29
4.3 保证接口的纯洁性 33
4.4 最佳实践 35
第5章 迪米特法则 36
5.1 迪米特法则的定义 36
5.2 我的知识你知道得越少越好 36
5.3 最佳实践 43
第6章 开闭原则 44
6.1 开闭原则的定义 44
6.2 开闭原则的庐山真面目 44
6.3 为什么要采用开闭原则 49
6.4 如何使用开闭原则 51
6.5 最佳实践 55
第二部分 真刀实枪—23种设计模式完美演绎
第7章 单例模式 58
7.1 我是皇帝我独苗 58
7.2 单例模式的定义 59
7.3 单例模式的应用 60
7.3.1 单例模式的优点 60
7.3.2 单例模式的缺点 60
7.3.3 单例模式的使用场景 61
7.3.4 单例模式的注意事项 61
7.4 单例模式的扩展 62
7.5 最佳实践 64
第8章 工厂方法模式 65
8.1 女娲造人的故事 65
8.2 工厂方法模式的定义 69
8.3 工厂方法模式的应用 70
8.3.1 工厂方法模式的优点 70
8.3.2 工厂方法模式的使用场景 71
8.4 工厂方法模式的扩展 71
8.5 最佳实践 77
第9章 抽象工厂模式 78
9.1 女娲的失误 78
9.2 抽象工厂模式的定义 83
9.3 抽象工厂模式的应用 86
9.3.1 抽象工厂模式的优点 86
9.3.2 抽象工厂模式的缺点 86
9.3.3 抽象工厂模式的使用场景 86
9.3.4 抽象工厂模式的注意事项 86
9.4 最佳实践 87
第10章 模板方法模式 88
10.1 辉煌工程—制造悍马 88
10.2 模板方法模式的定义 93
10.3 模板方法模式的应用 94
10.3.1 模板方法模式的优点 94
10.3.2 模板方法模式的缺点 95
10.3.3 模板方法模式的使用场景 95
10.4 模板方法模式的扩展 95
10.5 最佳实践 99
第11章 建造者模式 100
11.1 变化是永恒的 100
11.2 建造者模式的定义 109
11.3 建造者模式的应用 111
11.3.1 建造者模式的优点 111
11.3.2 建造者模式的使用场景 111
11.3.3 建造者模式的注意事项 111
11.4 建造者模式的扩展 111
11.5 最佳实践 112
第12章 代理模式 113
12.1 我是游戏至尊 113
12.2 代理模式的定义 116
12.3 代理模式的应用 118
12.3.1 代理模式的优点 118
12.3.2 代理模式的使用场景 119
12.4 代理模式的扩展 119
12.4.1 普通代理 119
12.4.2 强制代理 121
12.4.3 代理是有个性的 126
12.4.4 动态代理 128
12.5 最佳实践 134
第13章 原型模式 135
13.1 个性化电子账单 135
13.2 原型模式的定义 141
13.3 原型模式的应用 142
13.3.1 原型模式的优点 142
13.3.2 原型模式的使用场景 142
13.4 原型模式的注意事项 143
13.4.1 构造函数不会被执行 143
13.4.2 浅拷贝和深拷贝 144
13.4.3 clone与final两个冤家 146
13.5 最佳实践 146
第14章 中介者模式 147
14.1 进销存管理是这个样子的吗 147
14.2 中介者模式的定义 156
14.3 中介者模式的应用 159
14.3.1 中介者模式的优点 159
14.3.2 中介者模式的缺点 159
14.3.3 中介者模式的使用场景 159
14.4 中介者模式的实际应用 160
14.5 最佳实践 161
第15章 命令模式 162
15.1 项目经理也难当 162
15.2 命令模式的定义 170
15.3 命令模式的应用 173
15.3.1 命令模式的优点 173
15.3.2 命令模式的缺点 173
15.3.3 命令模式的使用场景 173
15.4 命令模式的扩展 173
15.4.1 未讲完的故事 173
15.4.2 反悔问题 174
15.5 最佳实践 175
第16章 责任链模式 178
16.1 古代妇女的枷锁—“三从四德” 178
16.2 责任链模式的定义 186
16.3 责任链模式的应用 189
16.3.1 责任链模式的优点 189
16.3.2 责任链模式的缺点 190
16.3.3 责任链模式的注意事项 190
16.4 最佳实践 190
第17章 装饰模式 192
17.1 罪恶的成绩单 192
17.2 装饰模式的定义 198
17.3 装饰模式应用 201
17.3.1 装饰模式的优点 201
17.3.2 装饰模式的缺点 201
17.3.3 装饰模式的使用场景 201
17.4 最佳实践 201
第18章 策略模式 203
18.1 刘备江东娶妻,赵云他容易吗 203
18.2 策略模式的定义 206
18.3 策略模式的应用 208
18.3.1 策略模式的优点 208
18.3.2 策略模式的缺点 208
18.3.3 策略模式的使用场景 209
18.3.4 策略模式的注意事项 209
18.4 策略模式的扩展 209
18.5 最佳实践 214
第19章 适配器模式 215
19.1 业务发展—上帝才能控制 215
19.2 适配器模式的定义 221
19.3 适配器模式的应用 223
19.3.1 适配器模式的优点 223
19.3.2 适配器模式的使用场景 224
19.3.3 适配器模式的注意事项 224
19.4 适配器模式的扩展 224
19.5 最佳实践 229
第20章 迭代器模式 230
20.1 整理项目信息—苦差事 230
20.2 迭代器模式的定义 236
20.3 迭代器模式的应用 239
20.4 最佳实践 239
第21章 组合模式 240
21.1 公司的人事架构是这样的吗 240
21.2 组合模式的定义 253
21.3 组合模式的应用 255
21.3.1 组合模式的优点 255
21.3.2 组合模式的缺点 256
21.3.3 组合模式的使用场景 256
21.3.4 组合模式的注意事项 256
21.4 组合模式的扩展 256
21.4.1 真实的组合模式 256
21.4.2 透明的组合模式 257
21.4.3 组合模式的遍历 259
21.5 最佳实践 260
第22章 观察者模式 262
22.1 韩非子身边的卧底是谁派来的 262
22.2 观察者模式的定义 271
22.3 观察者模式的应用 273
22.3.1 观察者模式的优点 273
22.3.2 观察者模式的缺点 274
22.3.3 观察者模式的使用场景 274
22.3.4 观察者模式的注意事项 274
22.4 观察者模式的扩展 275
22.4.1 Java世界中的观察者模式 275
22.4.2 项目中真实的观察者模式 276
22.4.3 订阅发布模型 277
22.5 最佳实践 277
第23章 门面模式 278
23.1 我要投递信件 278
23.2 门面模式的定义 283
23.3 门面模式的应用 284
23.3.1 门面模式的优点 284
23.3.2 门面模式的缺点 285
23.3.3 门面模式的使用场景 285
23.4 门面模式的注意事项 285
23.4.1 一个子系统可以有多个门面 285
23.4.2 门面不参与子系统内的业务逻辑 286
23.5 最佳实践 288
第24章 备忘录模式 289
24.1 如此追女孩子,你还不乐 289
24.2 备忘录模式的定义 294
24.3 备忘录模式的应用 297
24.3.1 备忘录模式的使用场景 297
24.3.2 备忘录模式的注意事项 297
24.4 备忘录模式的扩展 297
24.4.1 clone方式的备忘录 297
24.4.2 多状态的备忘录模式 300
24.4.3 多备份的备忘录 304
24.4.4 封装得更好一点 305
24.5 最佳实践 307
第25章 访问者模式 308
25.1 员工的隐私何在 308
25.2 访问者模式的定义 316
25.3 访问者模式的应用 320
25.3.1 访问者模式的优点 320
25.3.2 访问者模式的缺点 320
25.3.3 访问者模式的使用场景 320
25.4 访问者模式的扩展 321
25.4.1 统计功能 321
25.4.2 多个访问者 323
25.4.3 双分派 326
25.5 最佳实践 328
第26章 状态模式 329
26.1 城市的纵向发展功臣—电梯 329
26.2 状态模式的定义 341
26.3 状态模式的应用 343
26.3.1 状态模式的优点 343
26.3.2 状态模式的缺点 344
26.3.3 状态模式的使用场景 344
26.3.4 状态模式的注意事项 344
26.4 最佳实践 344
第27章 解释器模式 346
27.1 四则运算你会吗 346
27.2 解释器模式的定义 352
27.3 解释器模式的应用 354
27.3.1 解释器模式的优点 354
27.3.2 解释器模式的缺点 354
27.3.3 解释器模式使用的场景 355
27.3.4 解释器模式的注意事项 355
27.4 最佳实践 355
第28章 享元模式 356
28.1 内存溢出,司空见惯 356
28.2 享元模式的定义 361
28.3 享元模式的应用 364
28.3.1 享元模式的优点和缺点 364
28.3.2 享元模式的使用场景 364
28.4 享元模式的扩展 365
28.4.1 线程安全的问题 365
28.4.2 性能平衡 366
28.5 最佳实践 369
第29章 桥梁模式 371
29.1 我有一个梦想 371
29.2 桥梁模式的定义 379
29.3 桥梁模式的应用 381
29.3.1 桥梁模式的优点 381
29.3.2 桥梁模式的使用场景 382
29.3.3 桥梁模式的注意事项 382
29.4 最佳实践 382
第三部分 谁的地盘谁做主—设计模式PK
第30章 创建类模式大PK 384
30.1 工厂方法模式VS建造者模式 384
30.1.1 按工厂方法建造超人 384
30.1.2 按建造者模式建造超人 386
30.1.3 最佳实践 389
30.2 抽象工厂模式VS建造者模式 390
30.2.1 按抽象工厂模式生产车辆 390
30.2.2 按建造者模式生产车辆 394
30.2.3 最佳实践 399
第31章 结构类模式大PK 400
31.1 代理模式VS装饰模式 400
31.1.1 代理模式 400
31.1.2 装饰模式 402
31.1.3 最佳实践 403
31.2 装饰模式VS适配器模式 404
31.2.1 用装饰模式描述丑小鸭 404
31.2.2 用适配器模式实现丑小鸭 407
31.2.3 最佳实践 410
第32章 行为类模式大PK 411
32.1 命令模式VS策略模式 411
32.1.1 策略模式实现压缩算法 411
32.1.2 命令模式实现压缩算法 414
32.1.3 小结 419
32.2 策略模式VS状态模式 420
32.2.1 策略模式实现人生 420
32.2.2 状态模式实现人生 423
32.2.3 小结 425
32.3 观察者模式VS责任链模式 426
32.3.1 责任链模式实现DNS
解析过程 427
32.3.2 触发链模式实现DNS
解析过程 432
32.3.3 小结 437
第33章 跨战区PK 438
33.1 策略模式VS桥梁模式 438
33.1.1 策略模式实现邮件发送 439
33.1.2 桥梁模式实现邮件发送 442
33.1.3 最佳实践 445
33.2 门面模式VS中介者模式 446
33.2.1 中介者模式实现工资计算 446
33.2.2 门面模式实现工资计算 451
33.2.3 最佳实践 454
33.3 包装模式群PK 455
33.3.1 代理模式 455
33.3.2 装饰模式 457
33.3.3 适配器模式 459
33.3.4 桥梁模式 461
33.3.5 最佳实践 464
第四部分 完美世界—设计模式混编
第34章 命令模式+责任链模式 466
34.1 搬移UNIX的命令 466
34.2 混编小结 481
第35章 工厂方法模式+策略模式 483
35.1 迷你版的交易系统 483
35.2 混编小结 493
第36章 观察者模式+中介者模式 495
36.1 事件触发器的开发 495
36.2 混编小结 508
第五部分 扩展篇<
" 大旗不挥,谁敢冲锋
—6大设计原则全新解读
第1章 单一职责原则
第2章 里氏替换原则
第3章 依赖倒置原则
第4章 接口隔离原则
第5章 迪米特法则
第6章 开闭原则
单一职责原则
1.1 我是“牛”类,我可以担任多职吗
单一职责原则的英文名称是Single Responsibility Principle,简称是SRP。这个设计原则备受争议,只要你想和别人争执、怄气或者是吵架,这个原则是屡试不爽的。如果你是老大,看到一个接口或类是这样或那样设计的,你就问一句:“你设计的类符合SRP原则吗?”保准对方立马“萎缩”掉,而且还一脸崇拜地看着你,心想:“老大确实英明”。这个原则存在争议之处在哪里呢?就是对职责的定义,什么是类的职责,以及怎么划分类的职责。我们先举个例子来说明什么是单一职责原则。
只要做过项目,肯定要接触到用户、机构、角色管理这些模块,基本上使用的都是RBAC模型(Role-Based Access Control,基于角色的访问控制,通过分配和取消角色来完成用户权限的授予和取消,使动作主体(用户)与资源的行为(权限)分离),确实是一个很好的解决办法。我们这里要讲的是用户管理、修改用户的信息、增加机构(一个人属于多个机构)、增加角色等,用户有这么多的信息和行为要维护,我们就把这些写到一个接口中,都是用户管理类嘛,我们先来看它的类图,如图1-1所示。
太Easy的类图了,我相信,即使是一个初级的程序员也可以看出这个接口设计得有问题,用户的属性和用户的行为没有分开,这是一个严重的错误!这个接口确实设计得一团糟,应该把用户的信息抽取成一个BO(Business Object,业务对象),把行为抽取成一个Biz(Business Logic,业务逻辑),按照这个思路对类图进行修正,如图1-2所示。
重新拆封成两个接口,IUserBO负责用户的属性,简单地说,IUserBO的职责就是收集和反馈用户的属性信息;IUserBiz负责用户的行为,完成用户信息的维护和变更。各位可能要说了,这个与我实际工作中用到的User类还是有差别的呀!别着急,我们先来看一看分拆成两个接口怎么使用。OK,我们现在是面向接口编程,所以产生了这个UserInfo对象之后,当然可以把它当IUserBO接口使用。也可以当IUserBiz接口使用,这要看你在什么地方使用了。要获得用户信息,就当是IUserBO的实现类;要是希望维护用户的信息,就把它当作IUserBiz的实现类就成了,如代码清单1-1所示。
图1-2 职责划分后的类图
代码清单1-1 分清职责后的代码示例
......
IUserInfo userInfo = new UserInfo();
//我要赋值了,我就认为它是一个纯粹的BO
IUserBO userBO = (IUserBO)userInfo;
userBO.setPassword(""abc"");
//我要执行动作了,我就认为是一个业务逻辑类
IUserBiz userBiz = (IUserBiz)userInfo;
userBiz.deleteUser();
......
确实可以如此,问题也解决了,但是我们来分析一下刚才的动作,为什么要把一个接口拆分成两个呢?其实,在实际的使用中,我们更倾向于使用两个不同的类或接口:一个是IUserBO,一个是IUserBiz,类图如图1-3所示。
以上我们把一个接口拆分成两个接口的动作,就是依赖了单一职责原则,那什么是单一职责原则呢?单一职责原则的定义是:应该有且仅有一个原因引起类的变更。
1.2 绝杀技,打破你的传统思维
解释到这里,估计你已经很不屑了,“切!这么简单的东西还要讲?!”好,我们来讲点复杂的。SRP的原话解释是:
There should never be more than one reason for a class to change.
这句话初中生都能看懂,不多说,但是看懂是一码事,实施就是另外一码事了。上面讲的例子很好理解,在实际项目中大家都已经这么做了,那我们再来看看下面这个例子是否好理解。电话这玩意,是现代人都离不了,电话通话的时候有4个过程发生:拨号、通话、回应、挂机,那我们写一个接口,其类图如图1-4所示。
我不是有意要冒犯IPhone的,同名纯属巧合,我们来看一个这个过程的代码,如代码清单1-2所示。
代码清单1-2 电话过程
public interface IPhone {
//拨通电话
public void dial(String phoneNumber);
//通话
public void chat(Object o);
//通话完毕,挂电话
public void hangup();
}
实现类也比较简单,我就不再写了,大家看看这个接口有没有问题?我相信大部分的读者都会说这个没有问题呀,以前我就是这么做的呀,某某书上也是这么写的呀,还有什么什么的源码也是这么写的!是的,这个接口接近于完美,看清楚了,是“接近”!单一职责原则要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情,看看上面的接口只负责一件事情吗?是只有一个原因引起变化吗?好像不是!
IPhone这个接口可不是只有一个职责,它包含了两个职责:一个是协议管理,一个是数据传送。dial()和hangup()两个方法实现的是协议管理,分别负责拨号接通和挂机;chat()实现的是数据的传送,把我们说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递过来的信号还原成我们听得懂的语言。我们可以这样考虑这个问题,协议接通的变化会引起这个接口或实现类的变化吗?会的!那数据传送(想想看,电话不仅仅可以通话,还可以上网)的变化会引起这个接口或实现类的变化吗?会的!那就很简单了,这里有两个原因都引起了类的变化。这两个职责会相互影响吗?电话拨号,我只要能接通就成,甭管是电信的还是网通的协议;电话连接后还关心传递的是什么数据吗?通过这样的分析,我们发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆分成两个接口,其类图如图1-5所示。
这个类图看上去有点复杂了,完全满足了单一职责原则的要求,每个接口职责分明,结构清晰,但是我相信你在设计的时候肯定不会采用这种方式,一个手机类要把ConnectionManager和DataTransfer组合在一块才能使用。组合是一种强耦合关系,你和我都有共同的生命期,这样的强耦合关系还不如使用接口实现的方式呢,而且还增加了类的复杂性,多了两个类。经过这样的思考后,我们再修改一下类图,如图1-6所示。
图1-5职责分明的电话类图
图1-6 简洁清晰、职责分明的电话类图
这样的设计才是完美的,一个类实现了两个接口,把两个职责融合在一个类中。你会觉得这个Phone有两个原因引起变化了呀,是的,但是别忘记了我们是面向接口编程,我们对外公布的是接口而不是实现类。而且,如果真要实现类的单一职责,这个就必须使用上面的组合模式了,这会引起类间耦合过重、类的数量增加等问题,人为地增加了设计的复杂性。
通过上面的例子,我们来总结一下单一职责原则有什么好处:
q 类的复杂性降低,实现什么职责都有清晰明确的定义;
q 可读性提高,复杂性降低,那当然可读性提高了;
q 可维护性提高,可读性提高,那当然更容易维护了;
q 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。
看过电话这个例子后,是不是想反思一下了,我以前的设计是不是有点问题了?不,不是的,不要怀疑自己的技术能力,单一职责原则最难划分的就是职责。一个职责一个接口,但问题是“职责”没有一个量化的标准,一个类到底要负责那些职责?这些职责该怎么细化?细化后是否都要有一个接口或类?这些都需要从实际的项目去考虑,从功能上来说,定义一个IPhone接口也没有错,实现了电话的功能,而且设计还很简单,仅仅一个接口一个实现类,实际的项目我想大家都会这么设计。项目要考虑可变因素和不可变因素,以及相关的收益成本比率,因此设计一个IPhone接口也可能是没有错的。但是,如果纯从“学究”理论上分析就有问题了,有两个可以变化的原因放到了一个接口中,这就为以后的变化带来了风险。如果以后模拟电话升级到数字电话,我们提供的接口IPhone是不是要修改了?接口修改对其他的Invoker类是不是有很大影响?
注意 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。"