在对象设计的过程中,“决定把责任放在哪儿”是最重要的事情之一。但无论使用对象技术多么娴熟,也无法保证在设计对象时一次做对。因此,需要进行重构,改变原有的设计。
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;
}
常用的做法:
- 建立一个扩展类,将它作为原始类的子类或者包装类。
- 在扩展类中加入转型构造函数。
- 在扩展类中加入新特性。
- 根据需要,将原始对象替换为扩展对象。
- 将针对原始类定义的所有外加函数版移到扩展类中。
网友评论