-- Extract Interface(提炼接口) : 多个客户端使用类中的同一组代码, 或者两个类的接口有相同的部分, 此时可以将相同的子集提炼到一个独立接口中;
-- Duplicate Observed Data(复制被监视的数据) : 一些领域数据放在GUI控件中, 领域函数需要访问这些数据; 将这些数据复制到一个领域对象中, 建立一个观察者模式, 用来同步领域对象 和 GUI对象的重要数据;
实例变量太多解决方案 : 使用 Extract Class (提炼类) 方法将一些变量提炼出来, 放入新类中;
-- 产生原因 : 如果一个类的职能太多, 在单个类中做太多的事情, 这个类中会出现大量的实例变量;
-- 实例变量多的缺陷 : 往往 Duplicate Code(重复代码) 与 Large Class(过大的类)是一起产生的;
-- 选择相关变量 : 选择类中相关的变量提炼到一个新类中, 一般前缀, 后缀相同的变量相关性较强, 可以将这些相关性较强的变量提炼到一个类中;
-- 子类提炼 : 如果一些变量适合作为子类, 使用Extract Subclass(提炼子类) 方法, 可以创建一个子类, 继承该类, 将提炼出来的相关变量放到子类中;
-- 多次提炼 : 一个类中定义了20个实例变量, 在同一个时刻, 只使用一部分实例变量, 比如在一个时刻只使用5个, 在另一时刻只使用4个 ... 我们可以将这些实例变量多次使用 提炼类 和 子类提炼方法;
代码太多解决方案 :
-- 代码多的缺陷 : 太多的代码是 代码重复, 混乱, 最终走向项目死亡的源头;
-- 简单解决方案 : 使用 Extract Method (提炼函数) 方法, 将重复代码提炼出来;
-- 提炼类代码技巧 : 使用 Extract Class(提炼类) 和 Extract Subclass(子类提炼) 方法对类的代码进行提炼, 先确定客户端如何使用这个类, 之后运用 Extract Interface(提炼接口) 为每种使用方式提炼出一个接口, 可以更清楚的分解这个类;
-- GUI类提炼技巧 : 使用 Duplicate Observed Data(复制被监视的数据) 方法, 将数据 和 行为 提炼到一个独立的对象中, 两边各保留一些重复数据, 用来保持同步;
4. 过长参数列 (Long Parameter List)
使用到的重构方法简介 : Replace Parameter with Method(以函数取代参数), Preserve Whole Object(保持对象完整), Introduce Parameter Object(引入参数对象);
-- Replace Parameter with Method(以函数取代参数) : 对象调用 函数1, 将结果作为 函数2 的参数, 函数2 内部就可以调用 函数1, 不用再传递参数了;
-- Preserve Whole Object(保持对象完整) : 将对象中的一些字段是函数的参数, 直接将对象作为函数的参数, 由传递多个参数改为传递封装好的对象;
-- Introduce Parameter Object(引入参数对象) : 将函数参数封装在一个对象中;
参数列过长 :
-- 函数数据来源 : ① 参数, 将函数中所需的数据都由参数传入; ② 将函数中所用到的数据设置在全局数据中, 尽量不要使用全局数据;
-- 对象参数 : 使用对象封装参数, 不需要把函数需要的所有数据用参数传入, 只需要将函数用到的数据封装到对象中即可;
-- 面向对象函数参数少 : 面向对象程序的函数, 函数所用的数据通常在类的全局变量中, 要比面向过程函数参数要少;
普通参数和对象参数对比 :
-- 参数过长缺陷 : 太多的参数会造成函数 调用之间的 前后不一致, 不易使用, 一旦需要更多数据, 就要修改函数参数结构;
-- 对象参数优点 : 使用对象传递函数, 如果需要更多的参数, 只需要在对象中添加字段即可;
参数的其它操作 :
-- 函数取代参数 : 在对象中 执行一个 函数1 就可以取代 函数2 的参数, 就要使用 Replace Parameter with Method(以函数取代参数) 方法;
-- 对象代替参数 : 函数中来自 同一个对象的 多个参数 可以封装在这个对象中, 可以将这个封装好的对象当做参数, 使用Preserve Whole Object(保持对象完整) 方法;
-- 创建参数对象 : 如果找不到合适的对象封装这些参数数据, 可以使用 Introduce Parameter Object(引入参数对象) 方法制造一个参数对象;
对象依赖与函数参数之间的平衡 : 二者是相对的, 下面一定要选择一种不利状况;
-- 避免依赖 : 函数参数传递对象, 那个函数所在的对象 与 这个参数对象依赖关系很紧密, 耦合性很高, 这时候就要避免依赖关系, 将数据从对象中拆出来作为参数;
-- 参数太长 : 如果参数太长, 或者变化太频繁, 就要考虑是否选择依赖;
5. 发散式变化 (Divergent Change)
对于这个在我所在的研发团队中这个问题很严重, 因为做的是远程医疗系统, 在Android上要支持许多医疗设备, 每次添加医疗设备都会死去活来;
使用到的重构方法简介 : Extract Class(提炼类);
期望效果 : 当我们添加新功能的时候, 只需要修改一个地方即可, 针对外界变化相应的修改, 只发生在单一类中, 如果做不到这一点, 就意味着程序有了坏味道 Divergent Change;
发散式变化 :
-- 出现效果 : 如果对程序进行例行维护的时候, 添加修改组件的时候, 要同时修改一个类中的多个方法, 那么这就是 Divergent Change;
-- 修改方法 : 找出造成发散变化的原因, 使用 Extract Class(提炼类) 将需要修改的方法集中到一个类中;
6. 霰弹式修改 (Shotgun Surgery)
使用到的重构方法简介 : Move Method(搬移函数), Move Field(搬移字段), Inline Class(内联化类);
-- Move Method(搬移函数) : 类A 中的 方法A 与 类B 交流频繁, 在类B中创建一个与 方法A 相似的 方法B, 从方法A 中 调用 方法B, 或者直接将方法A删除;
-- Move Field(搬移字段) : 类A 中的 字段A 经常被 类B 用到, 在类B 中新建一个字段B, 在类B 中尽量使用字段B;
-- Inline Class(内联化类) : 类A 没有太多功能, 将类A 的所有特性搬移到 类B中, 删除类A ;
霰弹式修改坏味道 : 遇到的每种变化都需要在许多不同类内做出小修改, 即要修改的代码散布于四处, 不但很难找到, 而且容易忘记重要的修改, 这种情况就是霰弹式修改;
-- 注意霰弹式修改 与 发散式变化 区别 : 发散式变化是在一个类受多种变化影响, 每种变化修改的方法不同, 霰弹式修改是 一种变化引发修改多个类中的代码;
-- 目标 : 使外界变化 与 需要修改的类 趋于一一对应;
重构霰弹式修改 :
-- 代码集中到某个类中 : 使用 Move Method(搬移函数) 和 Move Field(搬移字段) 把所有需要修改的代码放进同一个类中;
-- 代码集中到新创建类中 : 没有合适类存放代码, 创建一个类, 使用 Inline Class(内联化类) 方法将一系列的行为放在同一个类中;
-- 造成分散式变化 : 上面的两种操作会造成 Divergent Change, 使用Extract Class 处理分散式变化;