学习基础:熟悉《设计模式》和《重构》的概念,熟悉基本的Java语法和XML语法,熟悉Eclipse和JUnit的使用,有相对较好的英语基础。
学习过程:
先按P40的学习顺序读完序号1~9。理解每个重构模式的动机,尝试读懂示例中的代码(实在不懂就放过,找机会上机也能明白)。
在时间允许的条件下,可以重读和对书中代码重构,从而更加充分地理解重构与模式这两个重要的概念和实践方法。
学习目的:使自己编写的代码更容易被人读懂。
学习感悟:
代码的重构应该是一步步完成的,每次重构的部分不要超过自己的理解能力的5%。虽然这样操作略显繁琐,但是可以减轻头脑重构过程中的记忆强度,减少代码出错的机会。
代码的重构一定要配合JUnit(TDD,测试驱动开发)完成,再加上Git(版本管理)和Eclipse(IDE的重构工具)那就事半功倍了。
一、构造函数
(一)、构造函数的重构:
6.1. (P43)用Creation Method替换构造函数:用明确的创建方法代替类中的多个构造函数,避免创建新类时客户无法正确理解和使用构造函数的意图。
优点与缺点:
+ 相比构造函数能够更好地表达所创建的实例的意图;
+ 避免了构造函数的局限性,例如:两个构造函数不能通过相同的参数和不同的返回类型进行重载;
+ 更容易发现无用的创建代码;
- 非标准化的创建方式,例如:不是统一用new的方式实例化。
11.1.(P275) 构造函数链:把构造函数链接起来,减少代码的重复。特殊的构造函数调用更通用的构造函数,直到全包含构造函数(处理所有的构造函数调用)。
当构造函数数量过多时,可以使用Creation Method(6.1节)重构。
(二)、Factory的使用
6.3. (P60)用Factory封装类:将同一个包里面、实现了相同接口的多个类的实例化交由工厂完成。
优点与缺点:
+ 通过Creation Method简化不同种类实例的创建;
+ 通过隐藏类减少包结构的“概念重量”;
+ 通过共享公共接口实践“面向接口编程,而不要面向实现编程”的理念;
- 增加新的种类的实例时,必须修改或者增加新的Creation Method;
- 当客户只能获得Factory的二进制代码而不是源代码时,对Factory的制定将受到限制。
6.4. (P67)用Factory Method引入多态创建:将同样的超类,不同的创建过程统一交由工厂完成。
优点与缺点:
+ 减少因创建自定义对象而产生的重复代码;
+ 有效地表达了对象创建发生的位置,以及如何重写对象的创建;
+ 强制Factory Method使用的类必须实现统一的接口;
- 可能会向Factory Method的一些实现者传递不必要的参数。
算法重构
Strategy
7.2.(P102) 用Strategy替换条件逻辑
优点与缺点:
+ 通过减少或者去除条件逻辑使算法变得清晰易懂;
+ 通过把算法的变体搬移到类层次中简化了类;
+ 允许在运行时用一种算法替换另一种算法;
- 增加设计的复杂度;
- 增加了算法获取数据的复杂度。
Template Method
8.1. (P166)形成Template Method:通过将不同方法中相同的顺序执行步骤提取出来从而实现泛化。
优点与缺点:
+ 通过把不变行为搬移到超类,去除子类中的重复代码;
+ 简化并有效地表达了一个通用算法的步骤;
+ 允许子类很容易地定制一个算法;
- 当为了生成算法导致子类必须实现很多方法的时候,就增加了设计的复杂度。
Composite
XMLBuilder的例子依据的有(7.5)TagNode,(6.5)TagBuilder,(10.1)TagNode,代码中存在一些小问题,可以试着自己修改。
7.1. (P97)Composed Method(组合方法)模式:将代码组合成名字简单易懂的方法,再将这些方法组合成更大的方法,从而方便读懂代码。
优点与缺点:
+ 清晰地描述了一个方法所实现的功能以及如何实现;
+ 把方法分解成命名良好的、处在细节的同一层面上的行为模块,以此来简化方法;
- 可能会产生过多的小方法;
- 可能会使调试变得困难,因为程序的逻辑分散在许多小方法中。
7.5. (P143)Composite模式:隐式地构造出树形结构。
优点与缺点:
+ 封装重复的指令,如格式化、添加或删除结点;
+ 提供了处理相似逻辑增长的一般性方法;
+ 简化了客户代码的构造职责;
- 构造的隐式的树结构越简单,设计的时候就越复杂。
6.5. (P74)用Builder封装Composite:因为构造Composite有许多重复工作,并且工作流程是复杂而且容易出错的,通过Builder来处理构造细节来简化构造过程。
优点与缺点:
+ 简化了构造Composite的客户代码;
+ 减少了创建Composite的重复和易出错的问题;
+ 在客户代码和Composite之间实现了松耦合;
+ 允许对已经封装的Composite或复杂的对象创建不同的表示;
- 接口可能无法清楚地表达其本质意图。
10.1. Collecing Parameter(聚集参数)模式:是把一个对象传入到不同的方法中,从而在这些方法中收集信息。
经常与Composed Method模式一起使用。
优点与缺点:
+ 可以把很大的方法转换成更上的、更简单的方法;
- 少量地影响结果代码的运行速度。
8.2.(P172) 提取Composite:将同一个超类下的多个子类实现的同样的Composite提取到超类中。
优点与缺点:
+ 去除重复的类存储逻辑和类处理逻辑;
+ 能够有效地表达类处理逻辑的可继承性。
8.3.(P180) 用Composite替换一个对象和多个对象的分别:用Composite实现了既可以处理一个对象,也可以处理多个对象的同样的函数接口。
优点与缺点:
+ 去除了处理一个对象的方法和多个对象的方法中重复的代码;
+ 提供了处理一个对象和多个对象的统一函数方法;
+ 提供了处理多个对象的更丰富的方法(例如:OR表达式);
- 可能会在Composite的构造过程中要求类型安全的运行时检查。
Command
7.6.(P155)用Command替换条件调度程序:为动作创建Command,将这些Command存储在集合中,再通过集合取出对应的Command执行。
缺点与优点:
+ 提供了用统一方法执行不同行为的简单机制;
+ 允许在运行时改变所处理的请求,以及如何处理请求;
+ 仅仅需要很少的代码实现;
- 会增加设计的复杂度,有时还不如条件调度程序简单。
Adapter
8.6.(P208)提取Adapter:一个类适配了多个版本的组件、类库、API或其他实体,可以通过提取Adapter来统一接口。
缺点与优点:
+ 隔离不同版本的组件、类库或API之间的区别;
+ 使类只负责适配代码的一个版本;
+ 避免了频繁地修改代码;
- 因为Adapter带来的限制,阻止了客户调用Adapter没有提供的功能。
8.5.(P99)通过Adapter统一接口:使用客户代码与多个类交互时,遵循统一接口完成。
优点与缺点:
+ 客户通过相同的接口与不同的类交互;
+ 客户通过公共的接口与多个对象交互;
+ 客户与不同的类交互方式相同;
- 当类的接口可以改变的时候,增加了设计的复杂度。
Adapter模式与Facade模式的区别
Adapter用来适配对象;
Facade用来适配整个子系统;通常用来与遗留系统进行交互。
它们都可以使代码易于使用,但是它们的应用级别不同。
State
9.1.(P231)用类替换类型代码:保护字段从而避免不正确或者不安全的赋值。
采用类替换类型代码而不用枚举,是因为类可以考虑未来行为的扩展,但是现在Java的枚举功能更加强大了,所以可以根据自己的习惯来选择。
当重构过程中产生的类需要扩展包含更多行为时,就可以考虑(7.4.State替换状态改变条件语句)进行重构了。
缺点与优点:
+ 避免非法赋值和比较;
- 比使用不安全类型需要更多的代码。
7.4. 用State替换状态改变条件语句:简化复杂的状态转换逻辑
优点与缺点:
+ 减少复杂的状态转换条件逻辑;
+ 简化复杂的状态改变逻辑;
+ 提供观察状态改变逻辑的角度;
- 增加设计的复杂度。
9.3. 引入Null Object:替换掉null的判断逻辑,提供了对null的正确处理。
优点与缺点:
+ 不需要重复的null逻辑就可以避免null错误;
+ 通过最小化null测试简化了代码;
- 当系统不太需要null测试的时候,会增加设计的复杂度;
- 如果程序员不知道Null Object的存在,就会产生多余的null测试;
- 使维护变得复杂,拥有超类的Null Object必须重写所有新继承到的公共方法。
Singleton
6.6. 将Singleton内联化:把Singleton的功能搬移到需要它的类中,然后删除Singleton。
优点与缺点:
+ 使对象的协作变得更加明显和明确;
+ 保护了单一的实例,而且不需要特别的代码;
- 当在许多层次间传递对象实例比较困难的时候,增加了设计的复杂度。
9.2. 用Singleton限制实例化:减少内存的占用,提高运行的速度。
优点与缺点:
+ 改进性能;
- 变成全局访问;
- 当对象含有不能共享的状态时,重构无法进行。
Observer
8.4.(P190)用Observer替换硬编码的通知:
优点与缺点:
+ 使主题及其观察者访问松散耦合;
+ 支持一个或多个观察者;
- 增加设计的复杂度;
- 面对串联通知的时候,会进一步增加设计的复杂度;
- 当观察者没有从主题中删除的时候,会造成内存泄漏。
注:例子不熟悉,所以代码没太能领悟
Decorator
7.3. (P115)将装饰功能搬移到Decorator:
优点与缺点:
+ 将装饰功能从类中搬移去除可以简化类;
+ 有效地将类的核心职责与装饰功能区分开;
+ 可以去除几个相关类中重复的装饰逻辑;
- 改变了被装饰对象的对象类型;
- 会使代码变得难以理解与调试;
- 当Decorator组合产生负责影响的时候会增加设计的复杂度。
注:这部分的代码也不完备,理解起来有困难,建议大致了解作者思路就好了,Decorator应该是个应用比较广泛的模式,以后自己可以通过实践探索。
Decorator与Strategy的区别:
+ 都可以去除与特殊情况或选择性行为相关联的条件逻辑;
+ 都通过把条件逻辑搬移不对劲新的类中达到这一目的;
- Decorator主要把自己包装在一个对象之外
- Strategy则用在一个对象之中。
11.2.(P278)统一接口:找出所有子类的公共方法,复制到超类中,在超类中执行空行为。
11.3.(P280)提取参数:通过客户代码提供的参数对字段进行赋值。
注:这两节都是辅助Decorator进行重构的。
6.2.(P51)将创建知识搬移到Factory:避免创建代码到处蔓延。
优点与缺点:
+ 合并创建逻辑和实例化配置选项;
+ 将客户代码与创建逻辑解耦;
- 如果可以直接实例化,用Factory就会增加设计复杂度。
注:可能作者认为采用的是HTMLParser的原因,所以给出的代码不全,不利于理解重构的含义。
Visitor
10.2.(P259)将聚集操作搬移到Visitor:适用于从多个对象中聚集信息,适用于从不同的对象中聚集信息。使用起来难度较大,首选应该是Collecting Parameter方法(P253,10.1.)。
优点与缺点:
+ 调节多个算法,使其余适用于不同的对象结构;
+ 访问相同或不同继承结构中的类;
+ 调用不同类上的类型特定方法,无需类型转换;
- 新的可访问类需要新的接收方法,每个Visitor中需要新的访问方法;
- 可能会破坏被访问类的封装性;
- 增加了代码的复杂度。
注:
- 尽量使用通用接口把互不相同的类转变成相似的类,而少用Visitor模式。
- Ralph Johnson:大多时候并不需要Visitor,有些时候则是必须要用,别无选择。
- 可能作者认为采用的是HTMLParser的原因,所以给出的代码不全,不利于理解重构的含义。
8.7.(P217)用Interpreter替换隐式语言
优点与缺点:
+ 比隐式语言更好的支持语言元素的组合;
+ 不需要新的代码来支持语言元素的新组合;
+ 允许行为的运行时配置;
- 会产生定义语法和修改客户代码的开销;
- 如果语言很复杂,编程工作量较大;
- 如果语言很简单,不需要考虑使用这个模式,否则会增加设计的复杂度。
2019.1.2.晨,结束阅读。学习代码下载
注:这种读书笔记对他人帮助不大,因为这个只是为了进一步理解作者的意图而进行的工作,通过输入关键点来确认自己是否真正领悟了作者想表达的意思。
当输入这些优点与缺点时并没有领悟,然后再通过输入代码来理解作者如何从一段不好的代码重构成一段好代码的,重构的过程中得到了什么、失去了什么,自己未来编写程序中应该如何平衡。
补充1:不要在意输入代码中出现的错误,那是因为代码不完备,许多类或者函数没有提供源代码。只要输入的代码可以排版和使用自动提示工具,就说明代码符合Java的语法规范,那么在阅读全书的过程中可以一点点把代码补全,或者根据自己的理解补全(这也是个理解重构和提高编码能力的机会)。
补充2:如果需要下载HTMLParser,请注意是1.3的版本才是作者使用的。但是与HTMLParser相关的例子与原代码并不完全相同,而作者并没有在自己的例子中把逻辑构造完整,因此建议跟HTMLParser相关的例子还是跳过吧,否则为了调试通过消耗时间太多。(我现在开始渴望直接看《重构》那本书了。)
代码目录:
贷款风险估算程序(6.1,7.2,8.1,11.1)
HTMLParser(6.2,7.3,8.2,10.1,10.2,11.2,11.3)
对象-关系数据映射(6.3)
XMLBuilder(6.4,6.5,7.5,8.5)
二十一点游戏(6.6)
集合类库(7.1)
SystemPermission(7.4,9.1,9.2)
Product(8.3,8.7)
CatalogApp(7.6)
JUnit(8.4)
数据库查询(8.6)
Applet(9.2)