重构类

中文名 英文名 时机 做法 意义
提炼类 Extract Class 1 一个大的类在处理多个不同的事情(这个类不纯洁了)
2 某个类做了应该有两个类做的事
3
4
①确定分出去的部分要做什么事情
②创建一个新的类,表示从旧地方分离出来的责任
③旧类创建时,为新类初始化
④使用搬移函数手法将需要的方法搬移到新的类(搬移函数时候就将调用地方改名)
⑤删除多余的接口函数,并为新类的接口取一个适合自己的名字
⑥考虑是否将新的类开放为公共类
单一职责
内联类 Inline Class 1 一个曾经有很多功能的类,在重构过程中,已经变成一个毫无单独职责的类
2 某个类没有做太多事情
3需要对两个类重新进行职责划分
①将需要内联的类中的所有对外可调用函数(也可能是字段)在目标类中新建一个对应的中间代理函数
②修改调用者,调用代理方法并测试
③将原函数中的相关方法(字段)搬移到新地方并测试
④原类变为空壳后就可以删除了
内聚
         
隐藏委托关系 Hide Delegate 1 一个类需要隐藏其背后的类的方法或事件
2 一个客户端调用类的方法时候,必须知道隐藏在后面的委托关系才能调用
重复】{
①在服务类(对外的类)中新建一个委托函数,让其调用受托类(背后的类)的相关方法
②修改所有客户端调用为这个委托函数
}
直到受托类全部被搬移完毕,移除服务类中返回受托类的函数
解耦
即使将来委托关系发生变化,变化也只会影响服务对象,而不会直接波及所有客户端。
移除中间人 Remove Middle Man 1 因为隐藏委托关系(当初可能是比较适合隐藏的)手法造成的现在转发函数越来越多
2 过度的迪米特法则造成的转发函数越来越多
①在服务类(对外)内为受托对象(背后的类)创建一个返回整个委托对象的函数
②客户端的调用转为连续的访问函数进行调用
③删除原本的中间代理函数

这能通过可自动化的重构手法来完成,你可以先对受托字段使用封装变量,再应用内联函数内联所有使用它的函数
AA
         
搬移函数 Move Function 1 随着对项目(模块)的认知过程中,也可能是改造过程中,一些函数已经脱离了当前模块的范围
2 一个模块内的一些函数频繁的与其他模块交互,却很少和自身内部进行交互(出现了叛变者)
3 一个函数在发展过程中,现在他已经有了更通用的场景
①查找要搬移的函数在当前上下文中引用的所有元素(先将依赖最少的元素进行搬离)
②考虑待搬移函数是否具有多态性(复写了超类的函数或者被子类重写)
③复制函数到目标上下文,调整函数,适应新的上下文
④函数内使用的变量考虑是一起搬移还是以参数传递
⑤改写原函数为代理函数(也可以内联)
⑥检查新函数是否可以继续进行搬离
内聚
搬移字段 Move Field 1 随着业务推进过程中,原有的数据结构已经不能很好的表示程序的逻辑
2 每当调用一个函数时,需要传入的记录参数,总是需要传入另一条记录或者他的某些字段一起
3 修改(行为)一条记录时,总是需要同时改动其他记录
4 更新(数据)一条字段时,总是需要同时在多个结构中作出修改
①源字段已经被封装(如果未封装,则应该先使用封装变量手法对其封装)
②目标对象上创建一个字段,及其访问函数
③源对象对目标对象的字段做对应的代理
④调整源对象的访问函数,令其使用目标对象的字段
⑤测试
⑥移除源对象的字段
⑦视情况而定决定是否需要内联变量访问函数
封装
         
搬移语句到函数 AA 1 重复代码
2 每次调用a方法时,b操作也总是每次都执行
3 <某些语句放在特定函数内更像一个整体
①将重复代码使用搬移函数手法到紧邻目标函数的位置
②如果目标函数紧被唯一一个原函数调用,则只需要将原函数的重复片段粘贴到目标函数即可
③选择一个调用点进行提炼函数,将目标语句函数与语句提炼成一个新的函数
④修改函数其他调用点,令他们调用新提炼的函数
⑤调整函数的引用点
⑥用内联函数手法将目标函数内联到新函数里
⑦移除原目标函数
⑧对新函数应用函数改名手法(改变函数声明的简单做法)
提炼
函数搬移到调用者 AA 1 随着系统前进过程中,函数某一块的作用发生改变,不再适合原函数位置
2 之前在多个地方表现一致的行为,如今在不同调用点面前表现了不同的行为

本手法只适合边界有些许偏移的场景,不适合相差较大的场景
①简单情况下,直接剪切
②将不想搬移的部分提炼成与当前函数同级函数(如果是超类方法,子类也要一起提炼)
③原函数调用新的同级函数
④替换调用点为新的同级函数和要内联的语句
⑤删除原函数
⑥使用函数改名手法(改变函数声明的简单做法)改回名字
AA
         
以函数调用替换内联代码 AA 1 函数内做的某些事情与已有函数重复
2 已有函数与函数之间希望同步变更
①内联代码替换为函数(可能有参数,就要对应传递) 封装
替换算法 AA 1 旧算法已经不满足当前功能
2 有更好的方式可以完成与旧算法相同的事情(通常是因为优化)
①保证待替换的算法为单独的封装,否则先将其封装
②准备好更好的算法
③替换算法
④运行并测试新算法与旧算法对比(一定要对比,也许你选的还不如以前呢)
策略
         
字段改名 AA 1 记录结构中的字段需要改个名字 如果结构简单,可以一次性替换
①如果记录没有封装,最好是先封装记录
②修改构造时候做兼容判断(老的值与新的值兼容判断:this.a = data.a OR data.b)
③修改内部设取值函数
④修改记录数据类中的内部调用
⑤测试
⑥修改外部调用初始化时候的数据
⑦删除初始化兼容判断
⑧使用函数改名手法(改变函数声明的简单做法),修改调用处的调用方式及内部取设值函数为新字段名
AA
以查询取代派生变量 AA 计算的参考变量,是不可变的,计算结果也是不可变的。可以不重构(还是那句话,不可变的数据,我们就没必要理他)
1 两个变量相互耦合
2 设置一个变量的同时,将另一个变量与该变量结合,通过计算后给另一个变量设置值
①确定可以引起变量发生变化的所有点(如果有来自其他模块变量,需要先用拆分变量手法)
②新建一个计算函数,计算变量值
③引入断言(assert),确保计算函数的值与该变量结果相同
④测试
⑤修改读取变量的代码,用内联函数手法将计算函数内联进来)
⑥用移除死代码手法将旧的更新点的地方清理掉
AA
         
将引用对象改为值对象 AA 1 几个对象中共享了一个对象,并且要联动变更的情况下
2 值对象就是每次设置都直接设置这个值,比如:
值对象:a.b=new b(1); 引用对象:a.b.c=1
①检查重构的目标是否为不可变对象,如果不是的话,则看看是否可以将其改为不可变对象
重复】{
②用移除设值函数手法去掉第一个设引用值函数(每次都用设置值的方式复写整个对象)
③测试
}
④判断两次相同输入时候,值是否相等
不可变数据
将值对象改为引用对象 AA 1 数据副本在多处使用,并且需要一处变化其他地方同步更新 ①创建一个仓库(如果没有的话),仓库要支持:每次访问相同数据都是一个相同的引用对象、支持注册新数据和获取同一个引用数据
②确保仓库的构造函数有办法找到关联对象的正确实例
③修改调用点,令其从仓库获取关联对象。
④测试
可变数据
         
AA AA 1
2
3
4







AA
AA AA 1
2
3
4







AA

处理概括关系

名称 定义 用法
字段上移(Pull Up Field) 两个子类拥有相同的字段。 将该字段移至超类。
函数上移(Pull Up Method) 有些函数,在各个子类中产生完全相同的结果。 将该函数移至超类。
构造函数本体上移(Pull Up ConstructorBody) 你在各个子类中拥有一些构造函数,他们的本体几乎完全一致。 在超类中新建一个构造函数,并在子类构造函数中调用它。
函数下移(Push Down Method) 超类中的某个函数只与部分(而非全部)子类有关。 将这个函数移到相关的那些子类去。
字段下移(Push Down Field) 超类中的某个字段只被部分(而非全部)子类用到。 将这个字段移到需要它的那些子类去。
提炼子类(ExtractSubclass) 类中的某些特性只被某些(而非全部)实例用到。 新建一个子类,将上面所说的那一部分特性移到子类中。
提炼超类(Extract Superclass) 两个类有相似特性。 为这两个类建立一个超类,将相同特性移至超类。
提炼接口(ExtractInterface) 若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。 将相同的子集提炼到一个独立接口中。
折叠继承体系(Collapse Hierarchy) 超类和子类之间无太大差别。 将它们合为一体。
塑造模板函数(Form TemPlateMethod) 你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上所有不同。 将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了。
然后将原函数上移至超类。
以委托取代继承(Replace Inheritance withDelegation) 某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。 在子类中新建一个字段用以保存超类;调整子类函数令它改而委托超类;然后去掉两者之间的继承关系。
以继承取代委托(Replace Delegation withInheritance) 你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。 让委托类来继承受托类。
14.大型重构**