MENU

《重构》第七章学习笔记

May 15, 2022 • 《重构》学习笔记

《重构》第七章学习笔记

在对象之间搬移特性

在对象设计过程中,“决定吧责任放在哪儿”是一件非常重要的事情,但是往往在编码过程中没办法保证一开始就做对,所以我们需要运用重构,改变自己原先的设计。

搬移函数(Move Method)

在程序中,有个函数与自身类之外的其他类进行更多的交流,调用其他类或者被其他类调用。在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变为一个单纯的委托函数,或是将旧函数完全移除。

动机
  • “搬移函数”是重构理论的支柱,如果一个类有太多行为或者和其它类有太多合作而形成高度耦合。
  • 使用另一个对象的次数比使用自身对象还要多。
做法
  • 检查源类中被源函数所使用的一切特性(包括字段和函数),考虑它们是否应该被转义。(如果某个特性只被打算搬移的那个函数使用,那么就应该一并搬移,有时候,搬移一组函数比逐一搬移简单些)。
  • 检查源类的子类和超累,看看是否有该函数的其他声明。
  • 在目标类中声明这个函数。
  • 将源函数复制到目标函数中,调整后者,使其能够正常运行。
  • 编译目标类。
  • 决定如何从源函数正确引用目标对象。
  • 修改源函数,使之作为一个纯委托函数。
  • 编译,测试。
  • 决定是否删除源函数,或者将其作为一个委托函数保留下来。
  • 如果要移除源函数,需要将源类中所有对源函数的引用都替换为目标函数的调用。

搬移字段(Move Field)

程序中,某个字段被其所在类之外的另一个类更多的用到,在目标类新建一个字段,修改源字段的所有用户,令它们改用新字段。

动机

  • 随着系统的发展,在这个星期看似合理而正确的设计决策,到下一个星期可能就不再正确,需要移动字段。
做法
  • 如果字段访问级是public,使用 Encapsulate Field 或者 Self Encapsulate Field 将其封装起来。
  • 编译测试。
  • 在目标类中建立与源字段相同的字段,并同时建立相应的设值/取值函数。
  • 编译目标类。
  • 决定如何在源对象中引用目标对象。
  • 删除源字段。
  • 将所有对源字段的引用都替换为目标函数的调用。
  • 编译,测试。

提炼类(Extract Class)

某个类做了应该由两个类做的事。
建立一个新类,将相关的字段和函数从旧类搬移到新类。

做法
  • 决定如何分解类所负的责任。
  • 建立一个新类,用以表现从旧类中分离出来的责任。(如果旧类剩下的责任与旧类名不符,则为旧类更名)。
  • 建立“从旧类访问新类”的连接关系。(有可能需要一个双向连接,不要建立“从新类通往旧类”的连接)。
  • 对于想要搬移的字段,使用Move Field进行搬移。
  • 每次搬移后,编译、测试。
  • 使用Move Method将必要的函数进行搬移,先搬移较低层的函数,再搬移叫高层的函数。
  • 每次搬移之后,编译、测试。
  • 检查,精简每个类的接口。
  • 决定是否公开新类。

将类内联化(Inline Class)

某个类没有做太多事情。
将这个类所有特性搬移到一个类中,然后移除源类。

动机
  • 如果一个类不再承担足够责任、不再有单独存在的理由。
做法
  • 在目标类身上声明源类的public协议,并将其所有函数委托至源类。
  • 修改所有源类引用点,改而引用目标类。
  • 编译、测试。
  • 运用 Move Method 和 Move Field 将源类的特性全部搬移到目标类。
  • 移除源类。

隐藏“委托关系”(Hide Delegate)

客户通过一个委托类来调用另一个对象。
在服务类上建立客户所需的所有函数,用以隐藏委托关系。

动机
  • 客户通过一个委托类调用另一个对象,在服务类上放置一个简单的委托函数,将委托关系隐藏起来。
做法
  • 对于每一个委托关系中的函数,在服务对象端建立一个简单的委托函数。
  • 调整客户,令它只调用服务对象提供的函数。
  • 每次调整后,编译并测试。
  • 如果将来没有任何用户需要使用委托类时就可以将其移除。
  • 编译测试。

移除中间人(Remove Middle Man)

某个类做了过多的简单委托动作。
让客户直接调用受托类。

动机
  • 每当客户要使用受托类的新特性时,你就必须再服务端添加一个简单委托函数。随着受托类的特性越来越多,这一过程会让你痛苦不已。服务类完全变成了一个“中间人”,这是就应该让客户直接调用受托类。
做法
  • 建立一个函数,用以获得受托对象。
  • 对于每个委托函数,在服务类中删除该函数,并让需要调用该函数的客户转为调用受托对象。
  • 处理每个委托函数后,编译、测试。

引入外加函数(Introduce Foreign Method)

你需要为提供服务的类增加一个函数,但你无法修改这个类。
在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。

动机
  • 服务类无法提供新功能,需要新加,如果使用该功能使用的多单独提炼出来 提高复用性。
做法
  • 在客户类中建立一个函数,用来提供你需要的功能。
  • 以服务实例作为该函数的第一参数。
  • 将该函数注释为:“外加函数(foreign method),应在服务类中实现”,这么一来,如果将来有机会将外加函数搬移到服务类中时,就可以轻松找出这些外加函数。

引入本地扩展(Introduce Local Extension)

你需要为服务类提供一些额外函数,但你无法修改这个类。
建立一个新类,使它包含这些额外函数,让这个扩展品成为源类的子类或者包装类。

动机
  • 难以控制额外增加的函数。
做法
  • 建立一个扩展类,将它作为原始类的子类或者包装类。
  • 在扩展类中加入转型构造函数。(所谓“转型构造函数”是指“接受原对象作为参数”的构造函数,如果采用子类话方案,那么转型构造函数应该调用适当的超类构造函数;如果采用包装类方案,那么转型构造函数因该将它得到的传入参数以实例变量的形式保存起来,用作接受委托的原对象)。
  • 在扩展类中加入特性。
  • 根据需要,将原对象替换为扩展对象。
  • 将针对原始类定义的所有外加函数搬移到扩展类中。