《设计模式之美》是由人民邮电出版社出版的一本关于设计模式方面的书籍,作者是王争,主要介绍了关于设计模式方面的知识内容,目前在设计模式类书籍综合评分为:8.4分。
书籍介绍
编辑推荐
从面向对象、设计原则、编程规范、代码重构铺开,Google前工程师带你追本溯源,帮助掌握编写高质量代码的所有知识。
(1)搞懂 23 种常用的设计模式,帮你跨越知识到应用的鸿沟
(2)200+ 真实代码分析设计与实现,手把手教你写出高质量代码;
(3)大互联网公司的编程经验分享,轻松应对设计模式面试的关卡;
(4)极客时间《设计模式之美》专栏4万+用户、《算法之美》10+万用户共同选择,经过读者验证的作品。
(5)通俗易懂,每个知识点结合实战代码讲解,达到学以致用的目标,如论新手或是工程师,无论求职面试或是项目开发,希望本书可以给出有益的指导。
内容简介
本书结合真实项目案例,从面向对象编程范式、设计原则、代码规范、重构技巧和设计模式5个方面详细介绍如何编写高质量代码。
第1章为概述,简单介绍了本书涉及的各个模块,以及各个模块之间的联系;第2章介绍面向对象编程范式;第3章介绍设计原则;第4章介绍代码规范;第5章介绍重构技巧;第6章介绍创建型设计模式;第7章介绍结构型设计模式;第8章介绍行为型设计模式。
本书可以作为各类研发工程师的学习、进阶读物,也可以作为高等院校相关专业师生的教学和学习用书,以及计算机培训学校的教材。
目录
- 第 1章概述 1
- 1.1 为什么学习代码设计 2
- 1.1.1 编写高质量的代码 2
- 1.1.2 应对复杂代码的开发 2
- 1.1.3 程序员的基本功 3
- 1.1.4 职场发展的技能 4
- 1.1.5 思考题 4
- 1.2 如何评价代码质量 4
- 1.2.1 可维护性(maintainability) 5
- 1.2.2 可读性(readability) 6
- 1.2.3 可扩展性(extensibility) 6
- 1.2.4 灵活性(flexibility) 6
- 1.2.5 简洁性(simplicity) 7
- 1.2.6 可复用性(reusability) 7
- 1.2.7 可测试性(testability) 7
- 1.2.8 思考题 8
- 1.3 如何写出高质量代码 8
- 1.3.1 面向对象 8
- 1.3.2 设计原则 8
- 1.3.3 设计模式 9
- 1.3.4 代码规范 9
- 1.3.5 重构技巧 10
- 1.3.6 思考题 11
- 1.4 如何避免过度设计 11
- 1.4.1 代码设计的初衷是提高代码质量 11
- 1.4.2 代码设计的原则是“先有问题,后有方案” 12
- 1.4.3 代码设计的应用场景是复杂代码 12
- 1.4.4 持续重构可有效避免过度设计 12
- 1.4.5 不要脱离具体的场景谈代码设计 13
- 1.4.6 思考题 13
- 第 2章面向对象编程范式 14
- 2.1 当我们在谈论面向对象时,到底在谈论什么 15
- 2.1.1 面向对象编程和面向对象编程语言 15
- 2.1.2 非严格定义的面向对象编程语言 16
- 2.1.3 面向对象分析和面向对象设计 16
- 2.1.4 关于UML的说明 17
- 2.1.5 思考题 17
- 2.2 封装、抽象、继承和多态为何而生 17
- 2.2.1 封装(encapsulation) 18
- 2.2.2 抽象(abstraction) 20
- 2.2.3 继承(inheritance) 21
- 2.2.4 多态(polymorphism) 22
- 2.2.5 思考题 25
- 2.3 如何进行面向对象分析、面向对象设计和面向对象编程 25
- 2.3.1 案例介绍和难点剖析 25
- 2.3.2 如何进行面向对象分析 26
- 2.3.3 如何进行面向对象设计 28
- 2.3.4 如何进行面向对象编程 34
- 2.3.5 思考题 35
- 2.4 面向对象编程与面向过程编程和函数式编程之间的区别 35
- 2.4.1 面向过程编程 36
- 2.4.2 面向对象编程和面向过程编程的对比 38
- 2.4.3 函数式编程 40
- 2.4.4 面向对象编程和函数式编程的对比 44
- 2.4.5 思考题 44
- 2.5 哪些代码看似面向对象编程风格,实则面向过程编程风格 45
- 2.5.1 滥用getter、setter方法 45
- 2.5.2 滥用全局变量和全局方法 47
- 2.5.3 定义数据和方法分离的类 49
- 2.5.4 思考题 50
- 2.6 基于“贫血”模型的传统开发模式是否违背OOP 51
- 2.6.1 基于“贫血”模型的传统开发模式 51
- 2.6.2 基于“充血”模型的DDD开发模式 52
- 2.6.3 两种开发模式的应用对比 53
- 2.6.4 基于“贫血”模型的传统开发模式被广泛应用的原因 57
- 2.6.5 基于“充血”模型的DDD开发模式的应用场景 58
- 2.6.6 思考题 59
- 2.7 接口和抽象类:如何使用普通类模拟接口和抽象类 59
- 2.7.1 抽象类和接口的定义与区别 59
- 2.7.2 抽象类和接口存在的意义 62
- 2.7.3 模拟实现抽象类和接口 64
- 2.7.4 抽象类和接口的应用场景 65
- 2.7.5 思考题 65
- 2.8 基于接口而非实现编程:有没有必要为每个类都定义接口 65
- 2.8.1 接口的多种理解方式 66
- 2.8.2 设计思想实战应用 66
- 2.8.3 避免滥用接口 69
- 2.8.4 思考题 69
- 2.9 组合优于继承:什么情况下可以使用继承 70
- 2.9.1 为什么不推荐使用继承 70
- 2.9.2 相比继承,组合有哪些优势 72
- 2.9.3 如何决定是使用组合还是使用继承 73
- 2.9.4 思考题 74
- 第3章设计原则 75
- 3.1 单一职责原则:如何判定某个类的职责是否单一 76
- 3.1.1 单一职责原则的定义和解读 76
- 3.1.2 如何判断类的职责是否单一 76
- 3.1.3 类的职责是否越细化越好 78
- 3.1.4 思考题 79
- 3.2 开闭原则:只要修改代码,就一定违反开闭原则吗 79
- 3.2.1 如何理解“对扩展开放、对修改关闭” 80
- 3.2.2 修改代码就意味着违反开闭原则吗 83
- 3.2.3 如何做到“对扩展开放、对修改关闭” 84
- 3.2.4 如何在项目中灵活应用开闭原则 85
- 3.2.5 思考题 86
- 3.3 里氏替换原则:什么样的代码才算违反里氏替换原则 86
- 3.3.1 里氏替换原则的定义 86
- 3.3.2 里氏替换原则与多态的区别 88
- 3.3.3 违反里氏替换原则的反模式 89
- 3.3.4 思考题 89
- 3.4 接口隔离原则:如何理解该原则中的“接口” 89
- 3.4.1 把“接口”理解为一组API或函数 90
- 3.4.2 把“接口”理解为单个API或函数 91
- 3.4.3 把“接口”理解为OOP中的接口概念 92
- 3.4.4 思考题 96
- 3.5 依赖反转原则:依赖反转与控制反转、依赖注入有何关系 97
- 3.5.1 控制反转(IoC) 97
- 3.5.2 依赖注入(DI) 98
- 3.5.3 依赖注入框架(DI Framework) 99
- 3.5.4 依赖反转原则(DIP) 100
- 3.5.5 思考题 100
- 3.6 KISS原则和YAGNI原则:二者是一回事吗 100
- 3.6.1 KISS原则的定义和解读 101
- 3.6.2 代码并非行数越少越简单 101
- 3.6.3 代码复杂不一定违反KISS原则 103
- 3.6.4 如何写出满足KISS原则的代码 104
- 3.6.5 YAGNI原则和KISS原则的区别 104
- 3.6.6 思考题 104
- 3.7 DRY原则:相同的两段代码就一定违反DRY原则吗 104
- 3.7.1 代码逻辑重复 105
- 3.7.2 功能(语义)重复 106
- 3.7.3 代码执行重复 107
- 3.7.4 代码的复用性 109
- 3.7.5 思考题 110
- 3.8 LoD:如何实现代码的“高内聚、低耦合” 110
- 3.8.1 何为“高内聚、低耦合” 110
- 3.8.2 LoD的定义描述 111
- 3.8.3 定义解读与代码示例一 112
- 3.8.4 定义解读与代码示例二 114
- 3.8.5 思考题 116
- 第4章代码规范 117
- 4.1 命名与注释:如何精准命名和编写注释 118
- 4.1.1 长命名和短命名哪个更好 118
- 4.1.2 利用上下文信息简化命名 118
- 4.1.3 利用业务词汇表统一命名 118
- 4.1.4 命名既要精准又要抽象 119
- 4.1.5 注释应该包含哪些内容 119
- 4.1.6 注释并非越多越好 120
- 4.1.7 思考题 120
- 4.2 代码风格:与其争论标准,不如团队统一 121
- 4.2.1 类、函数多大才合适 121
- 4.2.2 一行代码多长才合适 121
- 4.2.3 善用空行分割代码块 121
- 4.2.4 是四格缩进还是两格缩进 122
- 4.2.5 左大括号是否要另起一行 122
- 4.2.6 类中成员的排列顺序 122
- 4.2.7 思考题 123
- 4.3 编程技巧:小技巧,大作用,一招提高代码的可读性 123
- 4.3.1 将复杂的代码模块化 123
- 4.3.2 避免函数的参数过多 124
- 4.3.3 移除函数中的flag参数 125
- 4.3.4 移除嵌套过深的代码 126
- 4.3.5 学会使用解释性变量 128
- 4.3.6 思考题 129
- 第5章重构技巧 130
- 5.1 重构四要素:目的、对象、时机和方法 131
- 5.1.1 重构的目的:为什么重构(why) 131
- 5.1.2 重构的对象:到底重构什么(what) 131
- 5.1.3 重构的时机:什么时候重构(when) 132
- 5.1.4 重构的方法:应该如何重构(how) 132
- 5.1.5 思考题 133
- 5.2 单元测试:保证重构不出错的有效手段 133
- 5.2.1 什么是单元测试 133
- 5.2.2 为什么要编写单元测试代码 135
- 5.2.3 如何设计单元测试 136
- 5.2.4 为什么单元测试落地困难 138
- 5.2.5 思考题 139
- 5.3 代码的可测试性:如何编写可测试代码 139
- 5.3.1 编写可测试代码的方法 139
- 5.3.2 常见不可测试代码示例 146
- 5.3.3 思考题 147
- 5.4 解耦:哪些方法可以用来解耦代码 147
- 5.4.1 为何解耦如此重要 147
- 5.4.2 如何判断代码是否需要解耦 148
- 5.4.3 如何给代码解耦 148
- 5.4.4 思考题 150
- 5.5 重构案例:将ID生成器代码从“能用”重构为“好用” 150
- 5.5.1 ID生成器需求背景 150
- 5.5.2 “凑合能用”的代码实现 151
- 5.5.3 如何发现代码的质量问题 152
- 5.5.4 第 一轮重构:提高代码的可读性 153
- 5.5.5 第二轮重构:提高代码的可测试性 155
- 5.5.6 第三轮重构:编写单元测试代码 156
- 5.5.7 第四轮重构:重构异常处理逻辑 158
- 5.5.8 思考题 165
- 第6章创建型设计模式 166
- 6.1 单例模式(上):为什么不推荐在项目中使用单例模式 167
- 6.1.1 单例模式的定义 167
- 6.1.2 单例模式的实现方法 167
- 6.1.3 单例模式的应用:日志写入 170
- 6.1.4 单例模式的弊端 173
- 6.1.5 单例模式的替代方案 175
- 6.1.6 思考题 176
- 6.2 单例模式(下):如何设计实现一个分布式单例模式 177
- 6.2.1 单例模式的性 177
- 6.2.2 线程的单例模式 177
- 6.2.3 集群环境下的单例模式 178
- 6.2.4 多例模式 179
- 6.2.5 思考题 180
- 6.3 工厂模式(上):如何解耦复杂对象的创建和使用 180
- 6.3.1 简单工厂模式(Simple Factory Pattern) 181
- 6.3.2 工厂方法模式(Factory Method Pattern) 183
- 6.3.3 抽象工厂模式(Abstract Factory Pattern) 186
- 6.3.4 工厂模式的应用场景总结 187
- 6.3.5 思考题 187
- 6.4 工厂模式(下):如何设计实现一个依赖注入容器 188
- 6.4.1 DI容器与工厂模式的区别 188
- 6.4.2 DI容器的核心功能 188
- 6.4.3 DI容器的设计与实现 190
- 6.4.4 思考题 194
- 6.5 建造者模式:什么情况下必须用建造者模式创建对象 194
- 6.5.1 使用构造函数创建对象 194
- 6.5.2 使用setter方法为成员变量赋值 195
- 6.5.3 使用建造者模式做参数校验 196
- 6.5.4 建造者模式在Guava中的应用 198
- 6.5.5 建造者模式与工厂模式的区别 200
- 6.5.6 思考题 200
- 6.6 原型模式:如何快速复制(clone)一个哈希表 200
- 6.6.1 原型模式的定义 200
- 6.6.2 原型模式的应用举例 201
- 6.6.3 原型模式的实现方式:深拷贝和浅拷贝 203
- 6.6.4 思考题 206
- 第7章结构型设计模式 208
- 7.1 代理模式:代理模式在RPC、缓存和监控等场景中的应用 209
- 7.1.1 基于接口实现代理模式 209
- 7.1.2 基于继承实现代理模式 211
- 7.1.3 基于反射实现动态代理 211
- 7.1.4 代理模式的各种应用场景 212
- 7.1.5 思考题 213
- 7.2 装饰器模式:剖析Java IO类库的底层设计思想 213
- 7.2.1 Java IO类库的“奇怪”用法 213
- 7.2.2 基于继承的设计方案 215
- 7.2.3 基于装饰器模式的设计方案 215
- 7.2.4 思考题 219
- 7.3 适配器模式:如何利用适配器模式解决代码的不兼容问题 219
- 7.3.1 类适配器和对象适配器 219
- 7.3.2 适配器模式的5种应用场景 221
- 7.3.3 适配器模式在Java日志中的应用 224
- 7.3.4 Wrapper设计模式 226
- 7.3.5 思考题 230
- 7.4 桥接模式:如何将M×N的继承关系简化为M+N的组合关系 230
- 7.4.1 桥接模式的定义 230
- 7.4.2 桥接模式解决继承“爆炸”问题 230
- 7.4.3 思考题 231
- 7.5 门面模式:如何设计接口以兼顾接口的易用性和通用性 231
- 7.5.1 门面模式和接口设计 231
- 7.5.2 利用门面模式提高接口易用性 232
- 7.5.3 利用门面模式提高接口性能 232
- 7.5.4 利用门面模式解决事务问题 232
- 7.5.5 思考题 233
- 7.6 组合模式:一种应用在树形结构上的特殊设计模式 233
- 7.6.1 组合模式的应用一:目录树 233
- 7.6.2 组合模式的应用二:人力树 237
- 7.6.3 思考题 239
- 7.7 享元模式:如何利用享元模式降低系统的内存开销 239
- 7.7.1 享元模式在棋牌游戏中的应用 239
- 7.7.2 享元模式在文本编辑器中的应用 242
- 7.7.3 享元模式在Java Integer中的应用 244
- 7.7.4 享元模式在Java String中的应用 247
- 7.7.5 享元模式与单例模式、缓存、对象池的区别 248
- 7.7.6 思考题 248
- 第8章行为型设计模式 249
- 8.1 观察者模式:如何实现一个异步非阻塞的EventBus框架 250
- 8.1.1 观察者模式的定义 250
- 8.1.2 观察者模式的代码实现 250
- 8.1.3 观察者模式存在的意义 251
- 8.1.4 观察者模式的应用场景 253
- 8.1.5 异步非阻塞观察者模式 254
- 8.1.6 EventBus框架功能介绍 255
- 8.1.7 从零开始实现EventBus框架 257
- 8.1.8 思考题 261
- 8.2 模板方法模式(上):模板方法模式在JDK、Servlet、JUnit中的应用 261
- 8.2.1 模板方法模式的定义与实现 261
- 8.2.2 模板方法模式的作用一:复用 262
- 8.2.3 模板方法模式的作用二:扩展 264
- 8.2.4 思考题 266
- 8.3 模板方法模式(下):模板方法模式与回调有何区别和联系 267
- 8.3.1 回调的原理与实现 267
- 8.3.2 应用示例一:JdbcTemplate 268
- 8.3.3 应用示例二:setClickListener() 270
- 8.3.4 应用示例三:addShutdownHook() 271
- 8.3.5 模板方法模式与回调的区别 272
- 8.3.6 思考题 273
- 8.4 策略模式:如何避免冗长的if-else和switch-case语句 273
- 8.4.1 策略模式的定义与实现 273
- 8.4.2 利用策略模式替代分支判断 275
- 8.4.3 策略模式的应用举例:对文件中的内容进行排序 277
- 8.4.4 避免策略模式误用 281
- 8.4.5 思考题 281
- 8.5 职责链模式:框架中的过滤器、拦截器和插件是如何实现的 282
- 8.5.1 职责链模式的定义和实现 282
- 8.5.2 职责链模式在敏感词过滤中的应用 286
- 8.5.3 职责链模式在Servlet Filter中的应用 288
- 8.5.4 职责链模式在Spring Interceptor中的应用 290
- 8.5.5 职责链模式在MyBatis Plugin中的应用 293
- 8.5.6 思考题 297
- 8.6 状态模式:游戏和工作流引擎中常用的状态机是如何实现的 297
- 8.6.1 什么是有限状态机 298
- 8.6.2 状态机实现方式一:分支判断法 300
- 8.6.3 状态机实现方式二:查表法 301
- 8.6.4 状态机实现方式三:状态模式 303
- 8.6.5 思考题 306
- 8.7 迭代器模式(上):为什么要用迭代器遍历集合 306
- 8.7.1 迭代器模式的定义和实现 307
- 8.7.2 遍历集合的3种方法 309
- 8.7.3 迭代器遍历集合的问题 310
- 8.7.4 迭代器遍历集合的问题的解决方案 311
- 8.7.5 思考题 315
- 8.8 迭代器模式(下):如何实现一个支持快照功能的迭代器 315
- 8.8.1 支持快照功能的迭代器 315
- 8.8.2 设计思路一:基于多副本 316
- 8.8.3 设计思路二:基于时间戳 317
- 8.8.4 思考题 319
- 8.9 访问者模式:为什么支持双分派的编程语言不需要访问者模式 320
- 8.9.1 “发明”访问者模式 320
- 8.9.2 双分派(Double Dispatch) 328
- 8.9.3 思考题 330
- 8.10 备忘录模式:如何优雅地实现数据防丢失、撤销和恢复功能 330
- 8.10.1 备忘录模式的定义与实现 331
- 8.10.2 优化备忘录模式的时间和空间开销 333
- 8.10.3 思考题 334
- 8.11 命令模式:如何设计实现基于命令模式的手游服务器 334
- 8.11.1 命令模式的定义 334
- 8.11.2 命令模式的应用:手游服务器 335
- 8.11.3 命令模式与策略模式的区别 336
- 8.11.4 思考题 337
- 8.12 解释器模式:如何设计实现一个自定义接口告警规则的功能 337
- 8.12.1 解释器模式的定义 337
- 8.12.2 解释器模式的应用:表达式计算 337
- 8.12.3 解释器模式的应用:规则引擎 340
- 8.12.4 思考题 343
- 8.13 中介模式:什么时候使用中介模式?什么时候使用观察者模式? 343
- 8.13.1 中介模式的定义和实现 343
- 8.13.2 中介模式与观察者模式的区别 344
- 8.13.3 思考题 344