文章目录
-
- 1.重构的原则
- 2.代码的坏味道
- 3.第一组重构
- [4. 封装](#4. 封装)
-
- [4.1. 封装记录](#4.1. 封装记录)
- [4.2. 封装集合](#4.2. 封装集合)
- [4.3. 以对象取代基本类型](#4.3. 以对象取代基本类型)
- [4.4. 以查询取代临时变量](#4.4. 以查询取代临时变量)
- [4.5. 提炼类](#4.5. 提炼类)
- [4.6. 内联类](#4.6. 内联类)
- [4.7. 隐藏委托关系](#4.7. 隐藏委托关系)
- [4.8. 移除中间人](#4.8. 移除中间人)
- [4.9. 替换算法](#4.9. 替换算法)
- [5. 搬移特性](#5. 搬移特性)
-
- [5.1. 搬移函数](#5.1. 搬移函数)
- [5.2. 搬移字段](#5.2. 搬移字段)
- [5.3. 搬移语句到函数](#5.3. 搬移语句到函数)
- [5.4. 搬移语句到调用者](#5.4. 搬移语句到调用者)
- [5.5. 以函数调用取代内联代码](#5.5. 以函数调用取代内联代码)
- [5.6. 移动语句](#5.6. 移动语句)
- [5.7. 拆分循环](#5.7. 拆分循环)
- [5.8. 移除死代码](#5.8. 移除死代码)
- [6. 重新组织数据](#6. 重新组织数据)
-
- [6.1. 拆分变量](#6.1. 拆分变量)
- [6.2. 字段改名](#6.2. 字段改名)
- [6.3. 以查询取代派生变量](#6.3. 以查询取代派生变量)
- [6.4. 将引用对象改为值对象](#6.4. 将引用对象改为值对象)
- [6.5. 将值对象改为引用对象](#6.5. 将值对象改为引用对象)
- [7. 简化条件逻辑](#7. 简化条件逻辑)
-
- [7.1. 分解条件表达式](#7.1. 分解条件表达式)
- [7.2. 合并条件表达式](#7.2. 合并条件表达式)
- [7.3. 以卫语句取代嵌套条件表达式](#7.3. 以卫语句取代嵌套条件表达式)
- [7.4. 以多态取代条件表达式](#7.4. 以多态取代条件表达式)
- [7.5. 引入特例](#7.5. 引入特例)
- [7.6. 引入断言](#7.6. 引入断言)
- [8. 重构API](#8. 重构API)
-
- [8.1. 查询函数和修改函数分离](#8.1. 查询函数和修改函数分离)
- [8.2. 函数参数化](#8.2. 函数参数化)
- [8.3. 移除标记参数](#8.3. 移除标记参数)
- [8.4. 保持对象完整](#8.4. 保持对象完整)
- [8.5. 以查询取代参数](#8.5. 以查询取代参数)
- [8.6. 以参数取代查询](#8.6. 以参数取代查询)
- [8.7. 移除设值函数](#8.7. 移除设值函数)
- [8.8. 以工厂函数取代构造函数](#8.8. 以工厂函数取代构造函数)
- [8.9. 以命令取代函数](#8.9. 以命令取代函数)
- [8.10. 以函数取代命令](#8.10. 以函数取代命令)
- [9. 处理继承关系](#9. 处理继承关系)
-
- [9.1. 函数上移](#9.1. 函数上移)
- [9.2. 字段上移](#9.2. 字段上移)
- [9.3. 构造函数本体上移](#9.3. 构造函数本体上移)
- [9.4. 函数下移](#9.4. 函数下移)
- [9.5. 字段下移](#9.5. 字段下移)
- [9.6. 以子类取代类型码](#9.6. 以子类取代类型码)
- [9.7. 移除子类](#9.7. 移除子类)
- [9.8. 提炼基类](#9.8. 提炼基类)
- [9.9. 折叠继承体系](#9.9. 折叠继承体系)
- [9.10. 以委托取代子类](#9.10. 以委托取代子类)
- [9.11. 以委托取代基类](#9.11. 以委托取代基类)
1.重构的原则
2.代码的坏味道
- 神秘命名 :不能清晰表述含义和功能的命名。
重构手法:改变函数命名、变量改名、字段改名 - 重复代码 :一个以上地方出现相同的代码结构
重构手法: 提炼函数,移动语句、函数上移 - 函数过长 :需要注释来解释的时候,就要考虑是否需要拆解函数;条件表达式也是拆解函数的信号。
重构手法:提炼函数、以查询取代临时变量、引入参数对象、保持对象完整、以命令取代函数、分解条件表达式、以多态取代条件表达式、拆分循环。 - 参数列表过长 :从一个现有的数据结构中获取多个值时,可以直接使用数据结构;多个函数有同样的参数,可以将函数组合成类,把函数参数作为类的属性。
重构手法:以查询取代参数、保持对象完整、引入参数对象、移除标记参数、函数组合成类。 - 全局数据 :可以在代码的任何位置进行修改,带来隐患。
重构手法:封装变量 - 可变数据 :随着变量作用域的扩展,会使风险变大
重构手法:封装变量、拆封变量、移动语句、提炼函数、将查询函数和修改函数分离、移除设值函数、以查询取代派生变量、函数组合成类、函数组合成变换、将引用对象改为值对象。 - 发散式变换 :如果某个模块经常因为不同的原因在不同的方向上发生变换;增加某个功能,需要修改很多处。
重构手法:提炼函数、搬移函数、提炼类、拆分阶段。 - 散弹式修改 :每遇到某种变化,需要在许多不同的类内做许多小的修改
重构手法:搬移函数、搬移字段、函数组合成类、函数组合成变换、拆分阶段、内联函数、内联类。 - 依恋情结 :模块化即是力求将代码分出区域,最大化区域内部交互,最小化跨区域交互;一个函数跟另一个模块中的函数或者数据交流格外频繁,远胜于在自己所处模块内部的交流;将总是一起变化的东西放在一块儿。
重构手法:搬移函数、提炼函数 - 数据泥团 : 多个类中相同的字段,函数中相同参数,绑定在一起出现的参数。
重构手法:提炼类、引入参数对象、保持对象完整 - 基本类型偏执 :不要过分依赖基础类型,合适的时候封装为对象。
重构手法:以对象取代基本类型、以子类取代类型码、以多态取代条件表达式、提炼类、引入参数对象 - 重复的switch :强调重复的switch,维护时必须找到所有的switch语句。
重构手法:以多态取代条件表达式 - 用管道取代循环语句
- 冗赘的元素 :过于简单的结构、类或函数可以直接内联掉不需要在单独封装
重构手法:内联函数、内联类、折叠继承体系 - 过度设计 :不要为了未来的一些事情而使当前的代码进行特殊处理
重构手法:折叠继承体系、内联函数、内联类、改变函数声明、移除死代码 - 临时变量 :类内部某个字段仅为某特定情况而设置
重构手法:提炼类、搬移函数、引入特例 - 过长的消息链 :跨多层对象取值即是过长的消息链。
重构手法:隐藏委托关系、提炼函数、搬移函数 - 过度委托 :将一个类的大部分函数都委托给其他类,此时可移除中间类
重构手法:移除中间人、内联函数、以委托取代基类、以委托取代子类 - 内幕交易 :模块间进行大量的数据交互,应尽量减少。
重构手法:搬移函数、搬移字段、隐藏委托关系、以委托取代基类、以委托取代基类 - 类过大 :不要利用一个类做过多的事情,要遵循单一职责
重构手法:提炼类、提炼基类、以子类取代类型码 - 异曲同工类 :接口不一致的类无法互相替换。
重构手法:改变函数声明、搬移函数、提炼基类 - 纯数据类 :纯数据类即拥有一些字段,以及用于访问字段的函数,避免出现public字段。
重构手法:封装记录、移除设值函数、搬移函数、提炼函数、拆分阶段 - 被拒绝的遗赠 :基类中不应该有子类不想或不应该继承的数据和函数。
重构手法:函数下移、字段下移、以委托取代子类、以委托取代基类 - 注释 :当出现需要注释解释的代码,则表示需要通过提炼函数来解释其行为。
重构手法:提炼函数、搬移函数、引入断言
3.第一组重构
3.1.提炼函数
目的: 如果一段代码需要浏览全部代码才能知道它的功能,那么就应该提炼函数,以函数实现功能进行命名。
实现:
a.创造新函数并以功能进行命名,以做什么来命名,而不是怎么做来命名.
b.将待提炼代码拷贝到新函数中,并检查是否引用了作用域以外的函数或变量,如果有则以参数方式传入新函数。
c.在源函数中替换为新函数。
3.2.内联函数
目的:
a.内部函数代码和函数名一样清晰易读,就没有必要封装为函数
b.多个组织不合理的函数,可以将其合并为大函数再重新提炼。
实现:
a.检查函数,确定其不具备多态
b.找出函数所有调用点,替换为函数本体
c.删除函数定义
3.3.提炼变量
目的: 表达式复杂难读时,局部变量可以帮助将表达式分解为比较容易管理的形式
实现:
a.确定要提炼的表达式没有副作用
b.声明不可变变量,复制要提炼的表达式使其值赋值给新变量
c.用新变量取代表达式
3.4.内联变量
目的: 表达式比变量表达意思更清晰时,内联变量
实现:
a.确定变量右侧表达式没有副作用
b.将使用变量的地方替换为表达式
c.替换所所有使用变量的地方
d.删除变量的声明和赋值语句
3.5.修改函数名称
目的: 好的函数名,可以直接通过函数名看出函数的功能,不需要查看代码的实现
实现:
简单实现:
a.想要移除参数,需要确认函数体内没有使用该参数
b.修改函数名字
c.找到所有旧函数将其替换
迁移式实现:
a.有必要的化,先重构函数内部,方便后续步骤展开
b.使用提炼函数,将函数提炼成新函数
c.如果需要添加参数,采用简单做法添加
d.对旧函数使用内联函数
3.6.封装变量
目的:
a.提供一个清晰的观测点,由此监控数据的变化和使用
b.可以轻松添加数据被修改时的验证和后续逻辑
c.缩小变量的可见范围
实现:
a.创建封装函数,在其中访问和更新变量值
b.修改所有使用该变量的地方,使其调用封装函数
c.限制变量的可见性
3.7.变量改名
目的: 好的变量名可以解释一段程序在做什么
实现:
a.如果变量被广泛使用,考虑运用封装变量将其封装
b.修改所有使用该变量的地方
3.8.引入参数对象
目的: 一组数据结伴同行,多处被使用,可将其封装为数据结构
实现:
a.如果没有合适的数据结构,就创建一个
b.使用修改函数名称给原函数新增一个新建的数据结构作为参数
c.修改所有的调用处,使用新的数据结构
d.用数据结构中的元素,替换参数列表中的参数项,删除原来的参数
3.9.函数组合成类
目的: 一组函数形影不离的操作同一块数据,通常是将这块数据作为参数传递给函数
实现:
a.运用封装基类对多个函数公用的数据基类加以封装
b.对于使用该基类结构的每个函数,运用搬移函数将其移新类
c.用以处理该数据记录的逻辑可以用提炼函数提炼出来,并移入新类
3.10.函数组合成变换
目的: 有多个地方对关联的一组数据进行计算处理,可以将其合并到一起共同处理。以减少对关联数据做相似的处理,来减少代码重复。
实现:
a.创建一个变换函数,输入参数是需要变换的记录,并直接返回该记录的值
b.挑选一块逻辑,将其主体移入变换函数中,把结果作为字段添加到输出记录中,修改客户端代码,令其使用新字段
c.针对其他的相关计算逻辑,重复上述步骤
3.11.拆分阶段
目的: 一段代码在处理超过一件不同的事情时,需要拆分
实现:
a.将第二阶段的代码提炼成独立的函数
b.引入一个中转数据结构,将其作为参数添加到提炼出的新函数的参数列表中
c.检查第二阶段的参数,如果被第一阶段用到,将其移入中转数据结构中
d.对第一阶段的代码运用提炼函数,让提炼出的函数返回中转数据结构
4. 封装
4.1. 封装记录
目的: 封装可变数据,可以隐藏存储的细节和计算的过程
实现:
a.对持有记录的变量使用封装变量,将其封装到一个函数中
b.创建类,记录封装起来,将记录变量的值替换为类的实例,在类上定义访问接口,用于返回原始记录。修改封装变量的函数,令其使用这个访问函数
c.新建函数,让它返回该类的对象,而非原始的记录
d.使用新的访问函数,替换原来返回记录的函数。
e.删除类的原始记录的访问函数
4.2. 封装集合
目的: 封装可变数据,可以清楚的看到数据被修改的地方和修改方式,后续可以方便的修改数据结构。
实现:
a.如果集合的引用尚未被封装起来,先用封装变量封装它
b.在类上添加用于添加和删除集合元素的函数
c.查找所有引用点,如果调用方直接修改集合,令该处调用使用新的添加/移除元素函数。
d.修改集合中的取值函数,使其返回一个副本
4.3. 以对象取代基本类型
目的: 当基础类型变量的使用不再简单时封装为对象
实现:
a.如果变量尚未被封装起来,先使用封装变量封装它
b.创建一个类,类的构造函数保持这个数据值,并提供一个取值函数
c.修改设值函数,令其创建一个新类的对象并将其存入字段,如果有必要的话,同时修改字段的类型声明
d.修改取值函数,使其调用新类的取值函数,并返回结果
e.修改明确清楚的函数名字,将引用对象改为值对象或将值对象改为引用对象,用以明确新对象时值对象函数引用对象
4.4. 以查询取代临时变量
目的: 可以避免在多个函数中编写相同的逻辑
实现:
a.检查变量在使用前是否已经计算完毕,检查计算它的代码是否能得到相同的值
b.可将变量修改为只读
c.将变量赋值代码提炼为函数
d.应用内联变量手法移除临时变量
4.5. 提炼类
目的:
a.一个类应该是一个清晰的抽象的,只处理一些明确的责任。
b.当一个类不断新增功能,修改等变得复杂时,就要考虑将其功能进行分离。
实现:
a.决定如何分解类所负的责任
b.创建新类,用以表现从旧类中分离出来的责任。
c.构造旧类时,创建一个新类,建立旧类到新类的访问接口
d.将分离功能相关的字段和函数搬移到新类
e.去除不需要的接口
4.6. 内联类
目的: 如果一个类不再承担足够责任,不再有单独存在的理由
实现:
a.待内联类的public接口在目标类中创建对应函数
b.修改源类public接口的所有引用点,使其调用目标类的接口
c.将待内联类中的函数和数据搬移到目标类
d.删除待内联类
4.7. 隐藏委托关系
目的: 不要使用委托类去获取被委托类的实例,然后调用其接口;如果被委托类的接口改变,那么所有客户端都需要修改
实现:
a.对于每个委托关系中的函数,在委托类端创建一个简单的委托函数
b.调整客户端使其调用委托类的函数
c.如果将来不再有客户端使用委托类,便可以移除委托对象中的函数
4.8. 移除中间人
目的: 如果委托类的特性变多,那么相应的转发函数就会变多,此时可以考虑移除转发函数
实现:
a.为委托对象创建一个取值函数
b.对于每个委托函数,让其客户端转为连续的访问函数调用
4.9. 替换算法
目的: 用比较清晰的方式取代复杂的方式
实现:
a.整理待替换算法,保证它被提取到一个独立的函数中
b.进行独立函数测试
c.编写新算法
d.测试新旧算法,如果运行结果相同,则可替换新算法
5. 搬移特性
5.1. 搬移函数
目的:
a.提高代码的模块化
b.将关联紧密的上下文整合到一起
实现:
a.检查函数在当前上下文里引用的所有程序元素,考虑是否将它们一并搬移
b.检查待搬移函数是否具备多态性
c.将函数赋值一份到目标上下文中
d.从源上下文中正确引用目标函数
e.修改源函数使其成为一个委托函数
f.对源函数使用内联函数,去除委托
5.2. 搬移字段
目的:
a.如果修改一个数据结构时,总是需要同时修改另一条数据结构,那么说明字段放错了位置
b.如果更新一个字段,需要同时在多个结构中做修改,也是一个需要搬移的征兆
实现:
a.确保源字段已经得到了良好的封装
b.在目标对象上创建一个字段
c.确保源对象里能正常引用目标对象
d.调整源对象的访问函数,令其使用各目标对象的字段
e.移除源对象上的字段
5.3. 搬移语句到函数
目的: 如果调用某个函数时,总有一些相同的代码也需要每次执行,那么需要考虑将代码合并到函数里
实现:
a.将重复代码段移动到紧邻目标函数位置
b.如果目标函数只有一个调用点,那么将重复代码段移入目标函数中
c.不止一个调用点,运用提炼函数,将目标函数共同提炼成新函数
d.所有调用点替换为新函数
e.运用内联函数,将目标函数内联到新函数中
5.4. 搬移语句到调用者
目的: 函数边界发生偏移,即以往多个地方共用的行为,现在会出现不同的行为时,需要将不同的部分移除出函数,搬移到调用者
实现:
a.如果待搬移的语句仅有一处调用,那么直接搬移到调用函数中
b.如果有待搬移的语句有多处调用,那么需要先提炼函数,再搬移到调用函数中
5.5. 以函数调用取代内联代码
目的:
a.消除重复代码
b.如果内联代码时对已有函数的重复,此时使用已有函数替换内联代码
实现: 将内联代码替换为对一个既有函数的调用
5.6. 移动语句
目的: 让存在管理的东西一起出现,可以使代码更容易理解
实现:
a.确定待移动的代码片段应该搬往何处,检查待搬移代码段与目标之间是否会有影响
b.剪切待搬移代码段到目标位置
5.7. 拆分循环
目的: 为了减小多次循环次数,一个循环内做多件事情;那么修改循环时,就需要去理解两件事情
实现:
a.复制一遍循环代码
b.识别并删除循环中的重复代码,使每个循环只做一件事情
5.8. 移除死代码
目的:
a.阅读代码、理解软件运作原来时,无用代码确实会带来额外的思想负担。
b.没有用到的代码就删除掉,就算以后有可能使用到,在真正使用到的时候可通过版本控制来重新添加
实现:
a.查看死代码时候还有地方在调用
b.删除死代码
6. 重新组织数据
6.1. 拆分变量
目的: 临时变量被多次赋值时就需要拆分
实现:
a.在待分解变量的声明及第一次被赋值处,修改其名称
b.如果可以将新的变量声明为不可修改
c.以该变量第二次被赋值处为界,使其使用新变量
d.重复以上步骤,修改其他地方的赋值
6.2. 字段改名
目的: 数据结构中的字段,对于阅读者理解特别重要
实现:
a.如果作用域很小,则直接替换后测试
b.如果记录还没有封装,则先封装记录
c.在对象内部对私有字段改名,并调整内部访问该字段的函数
d.如果构造函数使用了旧的字段名,则使用修改函数名字将其改名
e.用于函数改名给访问函数改名
6.3. 以查询取代派生变量
目的:
a.可变数据是软件中最大的错误源头之一
b.在一处修改数据,可能在另一处造成难以发现的破坏
c.去除可变数据不现实,但尽量把可变数据的作用域限制在最小范围
实现:
a.找出所有对变量做更新的地方。如果有必要用拆分变量分割各个更新点
b.新建函数用于计算该变量的值
c.用引入断言,断言该变量和计算函数始终给出同样的值
d.修改读取该变量的代码,令其调用新建的函数
e.用移除死代码去掉变量的声明和赋值
6.4. 将引用对象改为值对象
目的:
a.如果为引用对象那么想要更新属性,则需要保留原对象不动,只更新属性
b.如果为值对象那么想要更新属性,可以直接替换整个内部对象
实现:
a.检查重构目标是否为不可变对象,或者是否可修改为不可变对象
b.用移除设值函数逐一去掉所有设值函数
c.提供过一个基于值的相等性判断函数,在其中使用值对象的字段
6.5. 将值对象改为引用对象
目的:
a.如果一个数据中可能包含多个记录,而这些记录都管理在同一个逻辑数据结构中时,即可使用引用对象,去除重复的逻辑数据结构。
b.去除重复的数据结构,可以降低内存的使用情况
c.对于修改来说,数据结构的副本在修改时需要找到所有的副本进行修改
实现:
a.为相关的对象创建一个仓库
b.确保构造函数有办法找到关联对象的正确实例
c.修改宿主对象的构造函数,令其从仓库中获取关联对象
7. 简化条件逻辑
7.1. 分解条件表达式
目的: 降低条件表达式的复杂度,减少当条件较多时函数的规模,提升代码可读性。
实现: 对条件判断和每个条件分支分别运用提炼函数手法
7.2. 合并条件表达式
目的:
a.检查条件不相同但最终行为却一致
b.如果认为检查项彼此独立,不能被视为同一次检查,那就不能合并
实现:
a.确定表达式没有副作用
b.使用适当的逻辑运算,将表达式合并
c.考虑对合并的表达式提炼为函数
7.3. 以卫语句取代嵌套条件表达式
目的: 某个条件为真,则做一些处理后,直接退出函数
实现:
a.选择最外层需要被替换的条件逻辑,替换为卫语句
b.如果多个卫语句引发同样的结果,则使用合并条件表达式合并
场景:
a.两个分支都属于正常行为
b.一个分支为正常行为,一个分支为异常行为
7.4. 以多态取代条件表达式
目的:
a.利用多态承载各个类型特有的行为,以去除重复的分支逻辑
b.有一个基础逻辑,在其之上又有一些差异。可将基础逻辑放进基类,差异点放进子类
实现:
a.如果类不具备多态,使用工厂函数创建并返回实例对象
b.调用方使用工程函数获取实例
c.带有条件逻辑的函数移动到基类中
d.重写基类中的条件逻辑函数,实现有差异的条件逻辑
7.5. 引入特例
目的: 一个数据结构的使用者都在检查某个特殊值,并且当这个特殊值出现时所作的处理都相同。如果有此问题,就要想办法把这个处理逻辑收拢到一起
实现:
a.给重构目标检查特例的属性,使其返回false
b.创建一个特例对象,其中只有检查特性的属性,返回false
c.对特例值比较的代码,提炼函数;使用到的地方使用新函数替换
d.将新的特例对象引入代码中,可以从函数调用中返回,也可以在变换函数中生成
e.修改特例函数的主题,函数中使用检查特例的属性
f.使用函数组合成类活函数组合成变换,将特例搬移到特例对象中
g.特例函数内联到使用到的地方
7.6. 引入断言
目的: 只用当某个条件为真时,该段代码才有效
实现: 如果发现代码某个添加始终为真,就加入一个断言
8. 重构API
8.1. 查询函数和修改函数分离
目的: 当查询和修改分离后,在任何地方都可以直接调用,不用操心其他的事情
实现:
a.复制整个函数,并以查询命名
b.从新函数中删除与查询不相关的语句
c.所有调用的地方进行替换
d.原函数中去掉返回值
8.2. 函数参数化
目的: 两个或多个函数相似,自由字面值不同,可使用传入不同的参数,将其合并去除重复代码
实现:
a.从相似的函数中选择一个
b.把需要作为参数的,添加到参数列表中
c.修改所有调用处
d.修改函数体,使其使用新传入的参数
e.对其所有相似函数进行修改
8.3. 移除标记参数
目的:
a.标记参数即函数内根据标记参数做不同的处理逻辑
b.传入的参数影响了函数内部的控制流,这才是标记参数
c.会隐藏掉函数调用中存在的差异
实现:
a.针对参数的每一种可能值,新建一个明确函数
b.使用标记参数的地方,改用新函数
8.4. 保持对象完整
目的:
a.可以更好的应对变化,如果将来需要增加字段,则可以方便扩展
b.可以缩短函数参数列表
实现:
a.新建空函数,给以期望的参数列表
b.新函数中调用旧函数,并把新参数映射到就函数参数列表中
c.替换所有旧函数的调用为新函数
d.将旧函数内联到新函数中
场景: 从一个函数获取几个值,然后有将这几个值传入另一个函数
8.5. 以查询取代参数
目的:
a.避免函数参数重复
b.缩短参数列表
实现:
a.如果有必要,将参数的计算过程到提炼到独立的函数中
b.函数体内引用的地方,修改为新提炼的函数
c.去掉参数
场景: 函数调用时传入一个值,但这个值函数自己可以容易获得
8.6. 以参数取代查询
目的:
a.改变代码的依赖关系
b.减少全局变量的引用
c.不要将所有依赖关系都变成参数,会导致参数列表冗长重复
实现:
a.对查询操作的代码提炼变量,将其从函数体中分离出来
b.对代码提炼函数
c.使用内联变量,消除刚才提炼出来的变量
d.对原来的函数使用内联函数
e.新函数改名为原函数
8.7. 移除设值函数
目的:
a.创建对象后,属性就不会改变的属性,去掉设值函数
b.只在构造函数中调用一次的设值函数
实现:
a.构造函数无法确定具体值时,将参数作为构造函数参数;在构造函数中调用设值函数,对字段设值
b.移除所有除了构造函数之外的设值函数调用,改为新的构造函数,并测试
c.使用内联函数消去设值函数。如果可以把字段设置为不可变类型
8.8. 以工厂函数取代构造函数
目的: 解决无法根据环境或参数信心返回子类的实例或代理对象问题
实现:
a.新建工厂函数,使其调用构造函数
b.将调用构造函数的代码修改为调用工厂函数
c.缩小构造函数的可见范围
8.9. 以命令取代函数
目的:
a.提供更大的灵活性和更强的表达能力
b.命令对象会增加代码复杂性
c.将复杂的函数拆解为多个方法,彼此之间通过成员变量进行数据共享
实现:
a.为需要封装的函数创建空类,并根据函数命名类
b.使用搬移函数将函数移动到类中
c.考虑给每个参数创建一个字段,在构造函数中添加对应的参数
8.10. 以函数取代命令
目的: 相对于命令对象,将处理简单的命令对象,变换为函数
实现:
a.运行提炼函数将命令对象的代码提炼到一个函数中
b.对命令对象中使用的函数进行内联函数方式合并到新函数中
c.将构造函数参数移动到新函数参数列表中
d.移除死代码并删除命令类
9. 处理继承关系
9.1. 函数上移
目的: 避免重复代码
实现:
a.检查待上移函数,确定它们完全一致
b.检查函数体内的函数和属性能在基类中调用到
c.基类中创建函数,并上移子类代码到基类新函数中
d.逐个移除子类函数
场景: 某个函数在各个子类中的函数体都相同
9.2. 字段上移
目的:
a.减少重复的成员变量的声明
b.有可能将使用改字段的行为从子类上移到基类中
实现:
a.针对待上移字段,检查它们的所有使用点,确认它们以同样的方式被使用
b.如果这些字段的名称不同,先使用变量改名为它们取个相同的名字
c.在基类中新建一个字段
d.移除子类中的字段
场景: 子类中出现重复的成员变量
9.3. 构造函数本体上移
目的: 消除构造函数中重复的语句
实现:
a.为基类创建构造函数
b.将子类构造函数中公共语句移动到基类构造函数调用语句之后
c.移除子类中的公用代码段。公共部分使用到的变量,作为基类的参数传给基类构造函数
d.对于无法简单上移到基类的公共代码,先应用提炼函数,再运用函数上移提升
9.4. 函数下移
目的: 如果基类中的某个函数只与一个(或少数几个)子类有关,那么将函数下移到子类中。避免非通用函数出现在基类中。
实现:
a.将基类的函数本体复制到每一个需要此函数的子类中
b.删除基类中的函数
c.将函数移动到每一个需要使用它的子类中
9.5. 字段下移
目的: 如果某个属性只被一个或者一小部分子类用到,那么将其下移到子类中,避免非通用成员变量在基类中。
实现:
a.在所有需要该属性的子类中声明属性
b.将其从基类移除
c.将该属性从所有不需要它的子类中删除
9.6. 以子类取代类型码
目的: 可以使用多态的方式处理条件语句
实现:
a.自封装类型码字段
b.任选一个类型码取值,创建子类,覆写类型码的取值函数,使其返回类型码的字面值
c.创建一个选择器逻辑,把类型码参数映射到新的子类
d.针对每个类型码取值,重复上述步骤
e.使用函数下移和以多态取代条件表达式处理原本访问类型码的函数
f.移除原有类型码的访问函数
9.7. 移除子类
目的: 子类功能变少,或者随着功能的改变被调整,不在需要时,去除子类
实现:
a.使用工厂函数取代构造函数,把子类的构造包装到基类的工厂函数中
b.如有任何代码检查子类的类型,先用提炼函数把类型检查逻辑包装起来,然后将其搬移到基类
c.新建一个属性表示子类类型
d.将原本用来判断子类类型的属性使用新建的子类类型
e.删除子类
9.8. 提炼基类
目的: 两个类在做相似的事情
实现:
a.新建类
b.使用构造函数本体上移'函数上移和属性上移手法,提取子类的共同元素
c.确保子类中的相同成分都上移到基类
d.考虑将调用的地方使用基类的接口
9.9. 折叠继承体系
目的: 基类和子类差异很小时,将两者合并
实现:
a.使用属性上移、属性下移、函数上移和函数下移将所有元素都移动到一个类中
b.即将删除的类的引用点修改为合并后的类
c.移除将删除的类
9.10. 以委托取代子类
目的:
a.降低继承之间的强依赖关系
b.可以使接口更清晰、减少耦合
实现:
a.如果构造函数有多个调用者,首先用以工厂函数取代构造函数,把构造函数包装起来
b.创建空的委托类,构造函数接受所有子类特有的数据项,并经常以参数的形式接受一个指向基类的引用
c.基类中添加属性,用于安放委托对象
d.修改子类的创建逻辑,使其初始化上述委托字段,放入委托对象示例
e.选择一个子类的函数移入委托类
f.使用搬移手法搬移上述函数
g.如果被搬移的源函数还在子类之外被调用了,就把留在源类中的委托代码从子类移到超类,并在委托代码之前加上卫语句,检查委托对象存在。如果子类之外已经没有其他调用者,就用移除死代码去掉已经没人使、用的委托代码。
h.重复上述步骤,直到子类中所有的函数都搬移到委托类
9.11. 以委托取代基类
目的:
a.通过继承来复用现有的功能。
b.基类的一些函数对子类不适用时,不应该使用继承获取基类的功能
c.用到现有类的部分功能时使用委托
实现:
a.子类中新建一个属性,使其引用基类的一个对象,并将这个委托引用初始化为基类的新示例
b.针对基类的每个函数,在子类中创建转发函数,将调用请求转发给委托引用
c.当所有超类函数都被转发函数覆写后,就可以去掉继承关系