《重构》学习笔记(05)-- 在对象之间搬移特性

在对象设计的过程中,“决定把责任放在哪儿”是最重要的事情之一。但无论使用对象技术多么娴熟,也无法保证在设计对象时一次做对。因此,需要进行重构,改变原有的设计。

Move Method(搬移函数)

简单的说就是将一个类中的函数搬移到另一个类中。


image

重构后


image

这种重构方式看似简单,其中的难点在于确定是否应该移动一个函数。通常,我们的做法如下:
  • 检查源类中被原函数使用的一切特性(包括字段和函数),考虑它们是否也该被搬移。
  • 检查源类中的子类和超类,看看是否有函数的其他声明。
  • 在目标类中声明这个函数。
  • 将源函数的代码复制到目标函数中。调整后者,使其能够正常运行。
  • 编译目标类。
  • 决定如何从源函数正确引用目标对象。
  • 决定是否删除源函数,或者将它当做一个委托函数保留下来。
  • 如果要移除源函数,将对所有源函数的所有调用,替换为对目标函数的调用。
  • 编译,测试。

Move Field(搬移字段)

Move Field与Move Method类似,将一个字段搬移到更适合的类中。


image

重构后


image

随着系统的发展,你会发现你需要新的类,并将现有的工作责任拖到新类中。通常我们的做法为:
  • 如果字段的访问级是public,使用Encapsulate Field(封装字段)将它封装起来。
  • 在目标类中建立与源字段相同的字段,并同时建立相应的设值、取值函数。
  • 决定如何在源对象中引用目标对象。
  • 删除源字段。
  • 将所有对源字段的引用替换为对某个目的函数的调用。

Extract Class(提炼类)

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


image

重构后


image

一个类应该是一个清楚的抽象,处理一些明确的责任。有些类含有大量的函数和数据,这样的类往往太大而不易理解,此时需要考虑哪些部分可以分离出去,并将它们分离到一个单独的类中。如果子类化只影响类的部分特性,或某些特性需要以一种方式来子类化,某些特性则需要以另一种方式子类化,这就意味需要分解原来的类。做法:
  • 决定如何分解类所负的责任。
  • 建立一个新类,用以表现从旧类中分离出来的责任。
  • 建立从旧类访问新类的连接关系。
  • 运用Mvoe Field搬移每个需要搬移的字段。
  • 运用Move Method搬移必要的函数。先搬移较低层函数,再搬移高层函数。
  • 决定是否要公开新类。

Inline Class(将类内联化)

如果某个类没有做太多事情,可以将这个类中所有的特性搬移到另一个类中,然后移除原类。


image

重构后


image

做法:
  • 在目标类身上声明源类的public协议,并将其中所有函数委托至源类。
  • 修改所有源类引用点,改而引用目标类。
  • 编译、测试。
  • 运用Move Method和Move Field,将源类的特性全部搬移到目标类。

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

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


image

重构为
[图片上传失败...(image-b9ec27-1560092577298)]
做法为:

  • 对于每一个委托关系中的函数,在服务对象端建立一个简单的委托函数。
  • 调整客户,令它只调用服务对象提供的函数。
  • 每次调整后,编译并测试。
  • 如果将来不再有任何客户需要取用Delegate(受托类),便可移除服务对象中的相关访问函数。
  • 编译、测试。

Remove Middle Man(移除中间人)

移除中间人与Hide Delegate是逆操作。如果某个类做了过多的简单委托动作,那么让客户直接调用受委托类。


image

重构为


image

委托的代价是,委托对象的变化会引起服务器对象变化。当服务类完成成了委托类的中间人,那么请删除中间人吧!不断使用Hide Delegate和Move Middle Man调整程序结构。做法为:
  • 建立函数,用以获得受托对象。
  • 对于每个委托函数,在服务类中删除该函数,并让需要调用该函数的客户转为调用受托对象。
  • 处理每个委托函数后,编译、测试。

Introduce Foreign Method(引入外加函数)

如果你正在使用一个类,但是你需要一项新的服务,这个类却无法修改或无法供应。此时你应该在客户类中新建一个函数,并以第一参数形式传入一个服务类的实例。
例如,我们需要获取一个Date的下一天。

Date newStart = new Date(previousEnd.getYear(),
                     previousEnd.getMonth(),previousEnd.getDate()+1);

可以重构为:

Date newStart = nextDay(previousEnd);
private static Date nextDay(Date arg){
    return new Date(arg.getYear(), arg.getMonth(),arg.getDate()+1);
}

在进行本项重构时, 如果你以外加函数实现一项功能, 那就是一个明确信号: 这个函数原本应该在提供服务的类中实现.但是不要忘记: 外加函数终归是权宜之计. 如果不能把外加函数搬移到服务类中, 就把外加函数交给服务类的持有者, 请他帮你在服务类中实现这个函数.本项重构一般的做法为:

  • 在客户类中建立一个函数,用来提供你需要的功能。
    =》这个函数不应该调用客户类的任何特性。如果它需要一个值,把该值当作参数传给它。
  • 以服务类实例作为该函数的第一个参数。

Introduce Local Extension(引入本地扩展)

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


image

重构为


image

例如以Date类为例,如果我们需要扩展,我们可以使用子类:
class MfDateSub extends Date{
    public MfDateSub nextDay()...
    public int dayOfYear()...
}

或者使用扩展类

class MfDateWrap{
    private Date _original;
}

常用的做法:

  • 建立一个扩展类,将它作为原始类的子类或者包装类。
  • 在扩展类中加入转型构造函数。
  • 在扩展类中加入新特性。
  • 根据需要,将原始对象替换为扩展对象。
  • 将针对原始类定义的所有外加函数版移到扩展类中。

你可能感兴趣的:(《重构》学习笔记(05)-- 在对象之间搬移特性)