《重构》读书笔记 Chapter 11

  1. Pull up Field
    • 场景:两个子类拥有相同的字段。
    • 简介:将该字段移至超类。
    • 动机:
      • 去除重复数据声明。
      • 去除重复行为。
    • 做法:
      1. 针对待提升的字段,检查它们的所有被使用点,确认它们以同样的方式被使用。
      2. 如果这些字段的名称不同,先将它们改名为你想赋予的统一命名。
      3. 编译,测试。
      4. 在超类中新建一个字段。
      5. 移除子类字段。
      6. 单元测试。
  2. Pull Up Method
    • 场景:有些函数,在各个子类中产生完全相同的结果。
    • 简介:将该函数移至超类。
    • 动机:
      • 避免行为重复。
    • 做法:
      1. 检查待提升函数,确定它们是完全一致的。
      2. 将函数签名修改为你想赋予的签名。
      3. 在超类中新建一个函数,将某一个待提升函数的代码复制过来。
      4. 移除所有待提升的子类函数,每移除一个进行单元测试。
      5. 观察该函数的所有调用者,看是否能改为使用超类对象。
  3. Pull Up Constructor Body
    • 场景:在各个子类中拥有一些构造函数,它们额本体几乎完全一致。
    • 简介:再超类中新建一个构造函数,在子类构造函数中调用它。
    • 动机:
      • 子类的建构过程相同。
    • 做法:
      1. 在超类中定义一个构造函数。
      2. 将子类构造函数中的共同代码搬移到超类构造函数中。
      3. 将子类构造函数中的共同代码删掉,改而调用新建的超类构造函数。
      4. 单元测试。
  4. Push Down Method
    • 场景:超类中的某个函数只与部分子类有关。
    • 简介:将这个函数移到相关的那些子类去。
    • 动机:
      • 超类中定义的应该是所有子类共有的方法,否则就丧失了多态的作用。
    • 做法:
      1. 在所有子类中声明该函数,将超类中的函数本体复制到每一个子类函数中。
      2. 删除超类中的函数。
      3. 编译,测试。
      4. 将该函数从所有不需要它的那些子类中删掉。
      5. 单元测试。
  5. Push Down Field
    • 场景:超类中的某个字段只被部分子类用到。
    • 简介:将这个字段移到需要它的那些子类去。
    • 动机:
      • 同Push Down Method一样,超类中定义的应该是所有子类共有的东西。
    • 做法:
      1. 在所有子类中声明该字段。
      2. 将该字段从超类中删除。
      3. 编译,测试。
      4. 将该字段从所有不需要它的那些子类中删掉。
      5. 单元测试。
  6. Extract Subclass
    • 场景:类中的某些特性只被某些实例用到。
    • 简介:新建一个子类,将上面所说的那一部分特性移到子类中。
    • 动机:
      • 某些特性只有一部分实例用到,证明其实这并不是一类特性,不应该放到一个类中,这时就有两种方法了,Extract Subclass和Extract Class,两者的抉择其实就是继承和委托的抉择。
    • 做法:
      1. 为源类定义一个新的子类。
      2. 为这个新的子类提供构造函数。
      3. 找出调用超类构造函数的所有地点,如果它们需要的是新建的子类,令它们改而调用新构造函数。
      4. 使用Push Down Method和Push Down Field将源类特性移到子类中去。
      5. 如果有用作类型码的字段,就用Replace Conditional with Polymorphism重构。
      6. 单元测试。
  7. Extract Superclass
    • 场景:两个类有相似特性。
    • 简介:为这两个类建立一个超类,将相同特性移至超类。
    • 动机:
      • 去掉重复。
    • 做法:
      1. 为原本的类新建一个空白的抽象超类。
      2. 用Pull Up Method、Pull Up Field和Pull Up Constructor Body逐一将子类的共同元素上移到超类。
      3. 检查留在子类中的函数,看是否有相同部分,然后使用Pull Up Method将相同部分提炼成函数移到超类。如果各子类中某个函数的整体流程很相似,可能可以用Form Template Method。
      4. 检查所有调用端,如果只使用了共同接口,那么将它们请求的对象类型改为超类。
  8. Extract Interface
    • 场景:若干客户使用类接口中的同一子集,或者两个类的接口有部分相同。
    • 简介:将相同的子集提炼到一个独立接口中。
    • 动机:
      • 分离责任,将某部分责任分离出来成为单独的接口,使得系统的用法更清晰。
    • 做法:
      1. 新建一个空接口。
      2. 让接口声明提炼类的共通操作。
      3. 让相关类实现这个接口。
      4. 调整客户端的类型声明,令其使用这个接口。
  9. Collapse Hierarchy
    • 场景:超类和子类之间无太大区别。
    • 简介:将它们合为一体。
    • 动机:
      • 继承体系很容易变得复杂,如果子类并未带来该有的价值,那么就需要把超类和子类合并起来。
    • 做法:
      1. 选择你想移除的类,超类还是子类。
      2. 使用本章前面的方式把行为和数据转移出去,每一个移动都进行编译和测试。
      3. 调整想移除类的所有引用点,让它们使用另一个类。
      4. 移除目标类,单元测试。
  10. Form Template Method
    • 场景:有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上有所不同。
    • 简介:将这些函数分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了,然后将原函数上移至超类。
    • 动机:
      • 避免重复,同时保持实质差异。
    • 做法:
      1. 在各个子类中分解目标函数,使分解后的各个函数要不完全相同,要不完全不同。
      2. 使用Pull Up Method将各子类内完全相同的函数上移至超类。
      3. 对于剩下的完全不同的函数,使用Rename Method将函数签名变得一致,这样,原函数将会变得一样。
      4. 使用Pull Up Method将所有原函数逐一上移至超类,在超类中将那些代表各种不同操作的函数定义为抽象函数。
      5. 移除子类中的原函数。
      6. 单元测试。
  11. Replace Inheritance with Delegation
    • 场景:某个子类只使用超类接口中的一部分,或是根本不需要继承而来的的数据。
    • 简介:在子类中新建一个字段用以保存超类,调整子类函数,令它改而委托超类,然后去掉两者之间的继承关系。
    • 动机:
      • 超类中的许多操作并不真正适用于子类。
      • 子类从超类中继承了一大堆子类并不需要的数据。
      • 超类中的某些protected函数对子类并没有什么意义。
      • 总的来说,这个类继承体系中的子类和超类并没有明显的衍生关系。
    • 做法:
      1. 在子类中新建一个字段,使其引用超类的一个实例,并将它初始化为this
      2. 修改子类内的所有函数,让它们不再使用超类,转而使用上面的受托字段。
      3. 去除两个类之间的继承关系,新建一个受托类的对象赋给受托字段。
      4. 针对客户端所用的每一个超类函数,为它添加一个简单的委托函数。
      5. 单元测试。
  12. Replace Delegation with Inheritance
    • 场景:在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。
    • 简介:让委托类继承受托类。
    • 动机:
      • 如果并没有使用受托类的所有函数,那么就不应该使用本重构。

        如果想解决委托函数过多的问题,还有其它选择:用Remove Middle Man去掉中间层,让客户端自己调用受托函数;或使用Extract Superclass将两个类接口相同的部分提炼到超类中,然后让两个类都继承这个超类;或使用Extract Interface。

      • 如果受托对象可变,并被其它对象共享,那么也不能使用本重构,因为这样就无法再共享数据了。

    • 做法:
      1. 让委托端成为受托端的子类。
      2. 将受托字段设为该字段所处对象本身。
      3. 去掉简单的委托函数。
      4. 编译并测试。
      5. 将所有其它涉及委托关系的代码,改为调用对象自身。
      6. 移除受托字段。