《重构》
本书目的:学习如何以一种可控制且高效率的方式进行重构。学会如何有条不紊地改进程序结构,而且不会引入错误。
预期:第一章至第十三章
第13章 重构,复用与现实
如何才能学会重构呢?(第一层)
本书给出了很多方法。是很好的例子。
面对一个既有的程序,该使用哪些重构呢?(第二层)
这取决于你的目标。一个常见的重构原因是调整程序结构以使(短期内)添加新功能更容易。
经验是无可替代的。
第一章
代码坏味道带来的问题是什么?
难以修改。找到修改点,也很容易在修改的时候出现BUG。
如果需求发生变化,原有代码难以复用。需要写两份相似的代码。
但是,接着带来的问题就是,在需要其内部某些逻辑的时候,你就需要重写两份代码。这就可能出现不一致的问题。
Lightning:如果你发现自己需要为程序添加一个新特性,而代码结构使你无法很方便地达成目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。
1.2 重构的第一步
构建测试环境。测试是重构的根本。
1.3 重构行为第二步:分解并重组statement()
代码块越小越容易管理。
第一个步骤就是找出代码的逻辑泥团并运用Extract Method
。
首先找出这段代码里局部变量和参数。不会修改的参数可以传入新的函数,会被修改的遍历需要小心。
在抽取函数之后,可以修改变量名称。
如果在抽取函数之后,发现未用到该类的相关信息,可以考虑是否将这个函数放错了位置。绝大多数情况下,函数应该放在它所使用的数据的所属对象内。这个方法叫做Move Method
。在迁移中,需要注意修改函数名以及传入参数。
在原来的类中,通过一个调用返回。
class Customer...
private double amountFor(Rental aRental) {
return aRental.getCharge();
}
有时候在修改搬移完函数之后,会想保留原来的函数。如果它是public的话,这样是ok的。
然后查看原来的函数,发现搬移完之后,某些变量可能是多余的,删去。尽量删去临时变量。如果这个变量在循环中,也可以将其从循环中剥离,构建一个函数,专门取拿这个值。重构时并不考虑性能 的降低,只在有优化的时候要考虑。
这样就可以复用代码写一个htmlStatement了。
1.4 运用多态取代与价格相关的条件逻辑
如果要修改的量并不是这个类的属性。那么建议进行Move。
那么在Move到的类,可以考虑使用多态取代与多个判断条件相关的逻辑。
但是考虑到一个Movie,可以修改自己所属的类型。但是你创建好了对象之后,并不好修改对象的类型。
可以采用State模式。只是将价格那个函数多态,创建一个基类Price。多类型的Price继承。
为了引入state模式。使用三种重构方法:
- Replace Type Code with State/stragety将与类型相关的行为搬移到state模式内
- move method将switch语句移到price
- replace conditional with ploymorphism去掉switch语句
第二章 重构原则
考虑重构的关键原则,以及重构时需要考虑的某些问题。
定义:在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。
名词定义:对软件内部结构的一种 调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构一般都是对软件的小改动,但重构之中还可以包含另一个重构。例如:Extract Class
通常包含Move Method
和Move Field
。
动词定义:使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
定义需要进一步扩展。重构的目的是使软件更容易被理解和修改。你可以在软件内部做很多修改,但必须对软件可观察的外部行为只造成很小变化,或甚至不造成变化。
使用重构技术开发软件时,你把自己的时间分配给两种截然不同的行为:
- 添加新功能
- 重构
在无论何时都要清楚自己是在做功能添加还是在做重构。
为何重构
- 改进软件设计
- 更容易理解
- 帮助找到BUG
- 提高编程速度
何时重构
重构不应该是一件应该特别拨出时间做的事情,重构应该随时随地进行。
三次法则
第一次做某件事时只管去做;第二次做类似的事情产生反感,但无论如何还是可以取做;第三次再做类似的事,你就要考虑重构。
- 添加新功能时重构:为了更快理解,便于添加新特性
- 修改错误时重构
- 复审代码时重构:这段代码可以用重构更好实现
重构的难题
- 数据库
- 修改接口
- 难以通过重构手法完成的设计改动
- 何时不该重构
2.6 重构与设计
把设计看作是软件开发的关键环节。
有一种观点认为:重构可以取代预先设计。这意思是你根本不必做任何设计,只管按照最初的想法开始编码,让代码有效运行,然后再将它重构成型。
有了重构,预先设计只要是 正确的即可。不必过多考虑预先设计的灵活性。
2.7 重构与性能
虽然重构可能使得软件运行更慢,但它也使软件的性能优化更容易。除了对性能有严格要求的实时系统,其他情况下“编写快速软件”的秘密是:首先写出可调的软件,然后调整它以求获得足够的速度。
第三章 代码的坏味道
何时去重构呢?主要是根据自己的判断。
3.1 重复代码
同一个类中使用相同的代码。
采用Extract Method提炼出重复的代码。
两个互为兄弟的子类内含相同的表达式。对两个类使用Extract Method
,然后将被提炼出来的代码使用Pull Up Method
,将它推入超类内。
如果这两个函数并非完全相同,那么就把相同部分提炼出来,不同的分隔开来,在采用FROM Template Method
获得一个Template Method
设计模式。
如果有些函数以不同的算法做同样的事情,可以选择一个较为清晰的函数,然后使用Substitute Algorithm
将其他函数替换掉。
如果两个毫不相关的类出现重复代码,应该考虑对其中一个使用Extract Class
,将重复代码提炼到一个独立类中,然后在另一个类中使用这个新类。
3.2 过长函数
应该积极分解函数。
遵循一条原则:每当感觉需要以注释说明点什么的时候,我们就把需要说明的东西写进到一个独立函数中,并以其用途进行命名。
99%的场合里一般使用抽取函数的方法。
如果抽取完之后,需要把许多的参数和临时 变量当作参数,导致可读性几乎没有任何提升。此时,可以采用Replace Temp with Query
来消除这些临时变量。Introduce Parameter Object
和Preserve Whole Object
则可以将过长的参数列变得更简洁一些。如果这么做了,还是有很多临时参数和变量,则可以使用Replace Method with Method Oject
。
条件表达式和循环常常也是提炼的信号。你可以使用Decompose Conditional
处理条件表达式。至于循环,你应该将循环和其内的代码提炼到一个独立函数中。
3.3 过大的类
单个类往往出现太多实例变量。
使用抽取类的方法,提炼时应该选择类内彼此相关的变量,将它们放在一起。通常如果类内的数个变量有着相同的前缀或字尾,这就意味着有机会将它们提炼到某个组件内。
如果这个组件适合作为一个子类,你会发现Extract Subclass
往往比较简单。
类内函数:抽取分解
类内代码过多:抽取类以及抽取子类。抽取接口提炼出一个接口。
3.4 过长参数列
如果向已有对象发出一条请求可以取代一个参数,那么你就应该激活重构手法Replace Parameter with Method
。
也可以使用Preserve Whole Object
将来自同一个对象的一堆数据收集起来,并以该对象替换它们。
如果某些数据缺乏合理的对象归属,可使用Introduce Parameter Object
为它们制造出一个参数对象。
3.5 发散式变化
就是说在需要修改的时候,有时候需要修改这个类中一部分函数,在另一次修改的时候,需要修改这个类中的另一部分函数。在这个时候就发生了发散式变化。
可以考虑将这个类分解为两个类。每个对象就可以只因一种变化而需要修改。
你应该找出某特定原有而造成的所有变化,然后运用抽取类将它们提炼到另一个类中。
3.6 霰弹式修改
如果每遇到变化,你都需要在许多不同的类内做出许多小的修改。
通过移动函数和移动字段的方式把所有需要修改的代码放进同一个类中。如果没有合适的类就创建一个。运用Inline Class
把一系列相关行为放进同一个类中。
3.7 依恋情结
函数对某个类的兴趣高过对自己所处类的兴趣。
把这个函数移到另一个地方。
一个函数用到了多个类的数据,那么我们要将其移动到哪里呢?
判断哪个类拥有最多被此函数使用的数据,然后就把这个函数和那些数据摆在一起。
3.8 数据泥团
问题:多个类使用相同的参数
解决:抽取相同参数到一个类。再在各个类中国引入这个对象。
3.9 基本类型偏执
将基本类型转换成类或对象进行引入。
3.10 switch惊悚现身
switch语句问题在于重复。同样的switch语句散布在不同的地方。
一般考虑多态解决。
3.11 平行继承体系
问题:为某个类增加一个子类,同时也需要为另一个类添加一个子类。
如果发现某个继承体系的类名前缀和另一个继承体系的类名前缀完全相同,就闻到坏味道了。
解决:让一个继承体系的实例引用另一个继承体系的实例。也可以进一步使用移动函数与移动字段方法。
3.12 冗赘类
重构之后类缩水;预想的变化没有发生,变得冗余。
解决:删去或使用collapse Hierarchy
。对于几乎没用的组件,使用inline class
3.13 夸夸其谈未来性
如果某个抽象类没有太大作用,请运用Collapse Hierarchy
。
不要的委托使用Inline Class
除掉。
如果函数的某些参数没有勇士,可以删去。
如果函数名称带有多余的抽象意味,应该重命名。
如果函数或类的唯一用户是测试用例,这就飘出了坏味道(夸夸其谈未来性)。
3.14 令人迷惑的暂时字段
是类的字段。但是只在某些函数中使用。
那么可以考虑将这些字段和函数放到另一个类中去。
3.15 过度耦合的消息链
连续地从对象中请求另一个对象。
通常比较好的方法:观察消息链最终得到的对象是用来干嘛什么的,看看能否抽取函数名,把使用该对象的代码提炼到一个独立函数中。再运用移动函数方法将这个函数推入消息链。
如果这个链上的某个对象想运用,就用函数来调用。
3.16 中间人
问题:类的过度委托
解决:某个类的接口有一半的函数都委托给其他类。可以直接和真正负责的对象打交道。
3.17 过于亲密
需要研究两个类的private。
解决:移动函数和移动字段。抽取类。
3.18 异曲同工的类
对于函数,重命名。
对于两个类,移动函数。如果这样不好实现,可以考虑构造一个超类。
3.19 不完美的库类
库类有时候是不完善的。
如果你只想修改库类的一两个函数,可以运用Introduce Foregin Method
。
如果想添加一大堆额外行为,就要运用Introduce Local Extension
3.20 纯稚的数据类
将数组字段private。
合理地管理设置与访问的函数。
3.21 被拒绝的遗赠
子类拒绝超类的某些函数或者数据。
传统说法,去构建一个兄弟类。把用不到的函数下推到兄弟类。
运用:Replace Inheritance with Delegation
来达到目的
3.22 过多的注释
在重构之后,代码一目了然。注释会显得有些多余。
但不是说注释不好。
第5章 重构列表
5.1 重构的记录格式
介绍重构时,作者采用一种标准格式。每个重构手法都有如下五个部分:
- 名称
- 简要概述:这一重构手法的适用场景,以及它所做的事情。
- 动机:介绍为什么需要这个重构以及什么情况下不该使用这个重构
- 做法:如何进行此重构
- 范例
查找引用点,用文本查询方式或编译器编一遍。或其他方式。
第6章 重新组织函数
什么情况下使用
过长的函数
怎么使用
采用提炼函数的方法。将一个函数调用动作替换为该函数本体。
如果在进行多次提炼之后,意识到提炼所得的某些函数并没有做任何实质性的事情,或者如果需要回溯恢复原先函数,我就需要考虑内联函数。
使用中可能的问题
- 处理局部变量:包括原先代码中的局部变量(可以考虑利用Replace Temp With Query的形式去替换)。如果很多地方使用了某个临时变量,可以先使用
Split Temptorary Variable
将它变得容易替换。但有时候临时变量太混乱,就需要使用Replace Method with Method Object(135)
,它的代价是引入一个新的类。 - 参数带来的问题比临时变量稍微少一些,前提是你在函数内赋值给它们。如果已经这样做了,就得使用
Remove Assignments to Parameters(131)
提炼函数
将一段代码放入一个独立函数中,并让函数名称解释该函数的用途。(以做什么来命名)
- 粒度小,复用的概率性大
- 高层函数读起来就像读注释一样
- 粒度小,修改更容易
面对的情况有:
- 无局部变量:直接
- 有局部变量:作为参数
- 对局部变量再赋值:通过函数返回改变的值,或者引用。
内联函数
在函数调用点插入函数本体,然后移除该函数。
面对的情况下:
- 不合理的函数提炼。让一些函数内联,有可能再提炼会更好。
- 删除无用的间接层调用
内联临时变量
有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。
将所有对该变量的引用动作,替换为对它赋值的那个表达式自身。
以查询取代临时变量
你的程序以一个临时变量保存某一表达式的运算结果。只在只有一次赋值的情况下,改变。
将这个表达式提炼到一个独立函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可被其他函数使用。
double basePrice = _quantity * _itemPrice;
if(basePrice > 1000) return basePrice * 0.95;
else return basePrice * 0.98;
/-----------
if(basePrice() > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
...
double basePrice() {
return _quantity * _itemPrice;
}
面对的情况:
- 临时变量迫使你写出更长的代码。写成一个函数,同一个类中会使用相同的变量。
- 如果多次赋值临时变量,考虑
Split Temporary Variable
引入解释性变量
你有一个复杂的表达式。
将该复杂表达式(或其中的一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
例如IF语句中很长的判断逻辑。
bool isMacOS = ;
bool isChecked = ;
分解临时变量
某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。
针对每次赋值,创造一个独立、对应的临时变量。
临时变量应该只赋值一次。如果它们被赋值超过一次,就意味着它在函数中承担了一个以上的责任。如果临时变量承担多个责任,它就应该被替换为多个临时变量,每个临时变量只承担一个责任。
移除对参数的赋值
代码对一个参数进行赋值。以一个临时变量取代该参数的位置。
int discount(int inputVal) {
int res = intputVal;
if(inputVal > 50) res -= 2;
}
主要避免与按引用传递混淆。
以函数对象取代函数
你有一个大型函数,其中对局部变量的使用使得你无法采用提炼函数。
将这个函数放进到一个单独对象中,如此一来局部变量就变成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小型函数。
替换算法
想把某个算法替换为另一个更为清晰的算法。
将函数本提替换为另一个算法。
第7章 在对象之间搬移特性
搬移函数
你的程序中,有个函数与其所驻类之外的另一个类进行更多的交流:需要调用另一个类,或者被另一个类调用。
在该函数最常用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。
搬移字段
在你的程序中,某个字段被其所驻类之外的另一个类更多地用到。
在目标类新建一个字段,修改源字段的所有用户,令它们改用新字段。
提取类
某个类做了应该由两个类做的事。
建立一个新类,将相关的字段和函数从旧类搬移到新类。
将类内联化
某个类没有做太多的事情。
将这个类的所有特性搬移到另一个类中,然后移除原类。
隐藏委托关系
如果某个用户通过 Server对象得到另一个对象,然后调用后者的函数,那么用户就需要知晓这一层委托关系。万一委托关系发生了变化,客户也会发生变化。
你可以在服务对象上放置一个简单的委托函数,将委托关系隐藏起来,从而去除这种依赖。
移除中间人
某个类做了过多的简单委托动作。那么让用户可以直接调用委托到的类。
引入外加函数
你无法修改一个类,但是你需要基于这个类做一些改动。
那么在使用的类中,添加一个函数,以无法修改的类作为入参,建立新的函数。
那么可以在使用类中直接调用。
引入本地扩展
你需要为服务类提供一些额外的函数,但你无法修改这个类。
建立一个新类,使它包含这些额外的函数。让这个类扩展成为源类的子类或包装类。
第8章 重新组织数据
自封装字段
为字段建立取值、设值函数,并且只以这些函数来访问字段。
以对象取代数据值
有个数据项,需要与其他数据和行为一起使用才有意义。
随着开发进行,简单数据项不再那么简单。那么就要考虑将这个数据项提取出来构成一个类。
将值对象改为引用对象
希望对一个实例进行的任意修改,同步到所有。
将这个值对象改为引用对象。
将引用对象改为值对象
你有一个引用对象,很小且不可变。考虑将其变为值对象。
值对象是不可变的。只要你调用同一个对象的同一个查询函数,应该得到相同的结果。
以对象取代数组
一个数组,其中的元素各自代表不同的东西。
考虑以对象替换数组。对于数组中的每个元素,以一个字段来表示。
Performance row = new Performance();
row.setName("x");
row.setWin("b");
复制“被监视的数据”
你有一些领域数据置身于GUI控件之中,而领域函数需要访问这些数据。
将该数据复制到一个领域对象中。建立一个Observer模式,用以同步领域对象和GUI对象内的重复数据。
将展示逻辑与业务逻辑分开。
将单向关联改为双向关联
两个类都需要使用对方的特性,但其间只有一条单向连接。
添加一个反向指针,并使得修改函数能够同时更新两条连接。
将双向关联改为单向关联
两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性。
去除不必要的关联。
以字面常量取代魔法数
创建一个常量,根据其意义为它命名,并将上述的字面数值替换为这个常量。
封装字段
将public字段,改为private并声明一个访问函数。
封装集合
有个类有个集合。
让这个类 返回该集合的一个只读副本呢,并在这个类中提供添加和删除集合元素的函数。
以数据类取代记录
可以创建一个类,专门表示一个记录型数据。
以类取代类型码
以一个新的类替换该数值类型码。使用static声明。每个数值是提炼类型的一个值。
在CPP当中,有枚举类。不用这个也可以。
以子类取代类型码
相当于多态,区分不同类型。
以State/Stategy取代类型码
你有一个类型码,它会影响类的行为,但是你无法通过继承的手法消除它。
你可以构建一个新的类型类,并在原来的类中添加,不同类型继承这个类型类,以区分。
以字段取代子类
你的各个子类的唯一差别只在返回常量数据的函数身上。
修改这些函数,使它们返回超类中的某个(新增)字段,然后销毁子类。
第9章 简化条件表达式
分解条件表达式
对于一个复杂的条件表达式。
从if、then、else三个段落中分别提炼出独立函数。
合并条件表达式
对于一系列条件测试,都得到相同的结果。
将这些测试合并为一个条件表达式,并将这个条件表达式提炼成为一个独立的函数。
合并重复的条件片段
在条件表达式的每个分支上有着相同的一段代码。
将这段重复的代码搬移到条件表达式之外。
移除控制标记
在一系列布尔表达式中,某个变量带有“控制标记”(例如循环中)的作用。
以break语句或return语句取代控制标记。更清晰。
以Guard Clauses取代嵌套条件表达式
函数中的条件逻辑使人难以看清正常的执行路径。
使用Guard Clauses表现所有特殊的情况。就是将if-else拆成独立的if。
double getPayAmount() {
double result;
if(_isRead) result = A();
else {
if(_isSeparated) result = B();
}
}
//----------------
double getPayAmount() {
double result;
if(_isRead) return A();
if(_isSeparated) return B();
return normalC();
}
以多态取代条件表达式
某个条件表达式,它根据对象类型的不同而选择不同的行为。
将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
double getSpeed() {
switch (_type) {
case A :
return A();
case B :
return B();
default :
return 0;
}
return 0;
}
//----
//每个子类继承基类 重写getSpeed
引入Null对象
你需要再三检查某个对象是否为NULL对象。
那么可以创建一个NULL对象,然后继承这个对象,进行修改,变成非NULL对象。
那么可以直接调用同样的方法,NULL对象返回NULL对象的,非NULL的返回非NULL的。
引入断言
某一段代码需要对程序状态做出某种假设。
以断言明确表现这种假设。
断言可以帮助程序阅读者理解代码所作的假设。
简化函数调用
函数改名
给函数命名的一个好办法:首先考虑应该给这个函数写上一句怎么样的注释,然后想办法将注释变成函数名称。
新创建一个函数,将旧函数的代码复制到新函数,然后在旧函数中直接调用新函数返回。编译测试。修改旧函数的点删除旧函数。
添加参数
某个函数需要从调用端得到更多的信息。为此函数添加一个对象参数,让该对象带进函数所需信息。
你必须修改一个函数,而修改后的函数需要一些过去没有的信息,因此你需要给该函数添加一个参数。
但是,过长的参数也会造成data clumps的坏味道。
在添加参数前,考虑一下现有参数:你能从这些参数得到所需要的信息码?如果回答是否定的,有可能通过某个函数提供所需信息码?你究竟把这些信息用于何处?这个函数是否应该属于拥有该信息的那个对象所有?看看现有的参数,是否可以考虑使用 引入参数对象解决?
移除参数
函数本体不再需要某个参数。将该参数移除。
多余的参数会造成多余的理解成本。
将查询函数和修改函数分离
某个函数既返回对象状态值,又修改对象状态。
建立两个不同的函数,其中一个负责查询,另一个负责修改。
如果一个函数可以任意调用且没有任何副作用,那么可以在任意地方调用它。
既有返回值又有副作用的函数,就应该试着将查询动作从修改动作中分割出来。
令函数携带参数
若干个函数做了类似的工作,但是在这些函数体却包含了不同的值。
建立一个单一函数,以参数表达那些不同的值。
以明确函数取代参数
你有一个函数,其中完全取决于参数值而采取不同行为。
针对该参数的每一个可能值,建立一个独立函数。
如果某个参数有多种可能的值,而函数内又以条件表达式检查这些值,并根据不同参数值做出不同的行为,那么就应该使用本重构。
好处:既然你为调用者提供了不同的函数调用,就可以避免出现条件表达式。此外你还可以获得编译期检查的好处,而且接口也更清楚。此外,你还可以获得编译期检查的好处,而且接口更加清楚。
如果以参数值决定函数行为,那么函数用户不但需要观察该函数,而且还要判断参数值是否合法,而合法的参数值往往很少在文档中被清楚地提出。
保持对象完整性
你从某个对象中取出若干个值,将它们作为某一次函数调用时的参数。应改为传递整个对象。
有时候,你会将来自同一个对象的若干项数据作为参数,传递给某个函数。这样做的问题在于:万一将来被调用函数需要新的数据项,你就必须查找并修改对此函数的所有调用。如果你把这些数据项所属的对象传给函数,那么久可以避免这种情况。
如果你传递的是整个对象,被调用函数所在的对象久需要依赖参数对象,如果这会使你的结构恶化,那么就不应该使用该方法。
如果被调用函数只需要参数对象的其中一个数值,那么只传递那个数值会更好。作者并不认同,其实从代码可读性来说,传一个数值和一个对象没有特别大的差异。更重要的考量应该放在对象之间的依赖关系。
如果被调用函数使用了来自另一个对象的很多项数据,这可能意味着该函数实际上应该被定义在那些数据的对象中。考虑 移动方法。
如果在运用该重构之前,没有定义一个完整对象。那么就应该先使用引入参数对象。
以函数取代参数
对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本身也能够调用前一个函数。
让参数接受者去除该项参数,并直接调用前一个函数。
动机:过长的参数列会增加程序阅读者的理解难度。
引入参数对象
某些参数总是很自然地同时出现。可以以一个对象取代这些参数。
可以减少参数列表,降低理解难度。
例如start、end。
移除设值函数
类中的某个字段应该在对象创建时被设值,然后就不再改变了。
去掉该字段的所有设值函数。
隐藏函数
将从来没有被其他任何类使用的函数设置为private。
当你面对一个过于丰富、提供了过多行为的接口时,就值得将非必要的取值函数和设值函数隐藏起来。
如果你把取值设值函数设置为private,然后在所有地方都直接访问变量,那么就可以放心移除设值取值函数了。
以工厂函数取代构造函数
在创建对象时不仅仅是做简单的构造动作。
将构造函数替换为工厂函数。
动机:就是在派生子类的过程中以工厂函数取代类型码。现在,创建名单中还得加上子类,那些子类也是根据类型码来创建。然而由于构造函数只能返回单一类型的对象,因此你需要将构造函数替换为工厂函数。
封装向下转型
某个函数返回的对象,需要由调用者执行向下转型(转变为子类)。
将向下转型动作放到函数中。
现在这种场景基本绝迹了。
以异常取代错误码
某个函数返回一个特定的代码,用以表示某种错误情况。
改用异常。
如果程序崩溃代价小的情况下。但在生产环境不能乱用呀。
这种方式可以清楚地将普通程序与错误处理分开。
以测试取代异常
在调用的时候,对于可以预先检查的条件,可以从抛出异常转变为在调用之前做检查。
try {
return array[periodNumber];
} catch(Exception &e) {
return 0;
}
//----------
if (periodNumber >= _values.length) return 0;
return _values[periodNumber];
异常不应该成为条件检查的替代品。
第11章 处理概括关系(继承关系)
字段上移
两个子类拥有相同的字段。那么将该字段放到超类。
函数上移
在各个子类中产生完全相同的结果。将该函数移动到超类。
构造函数本体上移
你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。
在超类中新建一个构造函数,并在子类构造函数中调用它。
函数下移
超类中的某个函数只与部分(而非全部)子类有关。将这个函数移到相关的那些子类中取。
字段下移
超类中的某个字段只与部分(而非全部)子类用到。将这个字段移到需要它的那些子类取。
提炼子类
类中的某些特性只被某些非全部的实例用到。新建一个子类,将上述的那一部分移动到子类中去。
提炼超类
两个类有相同的特性。为这两个类建立一个超类,将相同特性移动到超类。
提炼接口
若干用户使用类接口中的同一子集,或者两个类的接口有部分相同。
将相同的子集提炼到一个独立接口中。
折叠继承体系
超类和子类并无太大区别。将它们合二为一。
塑造模板函数
这里的模板并非指代C++中那种template关键字的模板函数。
有一些子类,它们相应的某些函数以相同的顺序执行类似的操作,但各个操作在细节上可能有差别。
将这些操作分别在超类中放进各个函数中,在子类中去重写。
以委托取代继承
某个子类只使用超类的一部分接口,或是根本不需要继承而来的数据。
在子类中新建一个字段用于保存本来的超类,调整子类函数,令它改而委托超类,然后去掉二者的继承关系。
以继承取代委托
你在两个类之间使用委托关系,并经创为整个接口编写许多极为简单的委托函数。
重构方式:让委托类继承受托类。
在使用所有受托类函数的情况下,考虑使用。
第12章 大型重构
梳理并分解继承体系
问题点:某个继承体系承担两项责任。
重构方式:建立两个继承体系,并通过委托关系让其中一个可以调用另一个。
将过程化设计转化为对象设计
问题点:传统过程化风格的代码。
重构方式:将数据记录变成对象,将大块的行为分称小块。并将行为移入相关对象之中。
将领域和描述/显示分离
问题点:某些GUI类之中包含了领域逻辑。
重构方式:将领域逻辑分离出来,为它们建立独立的领域类。
我理解相当于前后端分离。
提炼继承体系
问题点:某个类做了太多的工作,其中一部分工作是以大量条件表达式完成的。
重构方式:建立继承体系,以一个子类表示一种特殊情况。