在对象的设计过程中,[决定把责任放在哪儿]即使不是最重要的事,也是最重要的事之一。
你的程序中,有个函数与其所驻class之外的另一个class进行更多交流:调用后者,或被后者调用。在该函数最常引用(指涉)的class中建立一个有着类似行为的新函数,将旧函数变成一个单纯的委托函数(delegating method),或是将旧函数完全移除;
[函数搬移]是重构理论的支柱。
如果一个class有太多行为,或如果一个class与另一个class有太多合作而形成高度耦合(highly coupled),就应该搬移函数;
是否应该移动一个函数,这往往不是一个容易做出的决定,如果不能肯定是否应该移动一个函数,那么或许[移动这个函数与否]并不是那么重要。
我们用一个表示[账户]的class来说明这项重构:
假设有数种账户,每一种都有自己的[透支金计费规则]。所以透支金计费方法我们不应该放在账户里,我们希望能够把透支金计费方法overdraftCharge()放到AccountType类中。
上面,我们把透支金计费方法移到了账户类型AccountType类中,这样对于不同的账户透支金计费规则,可以使用多态来实现它的计费,而账户类只需要一个接口就可以。
另外,对此例中被移函数,只取用(指涉)了一个值域(_daysOverdrawn),所以我只需要将这个值域作为参数传给target method就可以了。如果被移函数调用了Account中的另一个函数的话,就不能这么简单的处理了,这种情况我必须将source object传递给teget method。
如果target method需要太多source object特性,就得进一步重构。通常这种情况下应该分解target method,并将其中一部分移回source class;
当target method需要使用source class特性的时候,有四种选择:
1 将这个特性也移到target class;
2 建立或使用一个从target class到source class的引用;
3 将source class当作参数传给target method;
4 如果所需特性是个变量,将它作为参数传给target method。
很明显,上述用的是第四种。具体用哪种看实际情况。
你的程序中,某个field被其所驻class之外的另一个class更多地用到。那么在target class中建立一个new field,修改source field的所有用户,令它们必胜new field;
在classes之间移动状态(states)和行为,是重构过程中必不可少的措施。如果对于一个field(值域),在其所驻class之外的另一个class中有更多函数使用了它,就可以考虑搬移这个field。所谓[使用]可能是通过设值/取值(setting/getting)函数间接进行。
下面是Account class的部分代码:
每种账户类型的利率不同,所以我想把表示利率的_interestRate搬移到AccountType中去。
某个class做了应该由两个classes做的事,建立一个新class,将相关的值域和函数从旧class搬移到新class。
一个class应该是一个清楚的抽象(abstract),处理一些明确的责任。
在实际工作中,class会不断成长扩展。你会在这加入一些功能,在那加入一些数据,给某个class添加一项新责任时,你会觉得不值得为这项责任分离出一个单独的class。于是,随着责任的不断增加,这个class会变得过份复杂。
如果某些数据和某些函数总是一起出现,如果某些数据经常同时变化甚至彼此相依,这就表示你应该将它们分离出去。
从一个简单的Person class开始:
这上面例子中,我们可以将[与电话号码相关]的行为分离到一个独立class中,如下所示:
正好与Extract class相反,如果你的某个class没有做太多事情(没有承担足够责任),就可以将class的所有特性搬移到另一个class中,然后移除原class。
如果一个class不再承担足够的责任、不再有单独存在的理由,就应该挑选这一[萎缩class]的最频繁的用户(也是个class),以Inline Class手法将[萎缩class]塞进去。
如Extract Class范例所示,可以将TelephoneNumber塞回到Person类去。
对于客户直接调用其server object(服务对象)的delegate class,在server端(某个class)建立客户所需的所有函数,用以隐藏委托关系(delegation);
[封装]即使不是对象的最关键特征,也是最关键特征之一。[封装]意味着每个对象都应该尽可能少了解系统的其他部分。如此一来,一旦发生了变化,需要了解这一变化的对象就会比较少,这会使得变化比较容易进行。
如果某个客户调用了[建立于server object的某个值域基础之上]的函数,那么客户就必须知道这一委托对象(即server object的特殊值域)。万一委托关系发生变化,客户也得相应变化。你可以在server端放置一个简单的委托函数(delegate method),将委托关系隐藏起来,从而去除这种依存性。这么一来即使将来发生委托关系的变化,变化将被限制在server中,不会波及客户。
我们从代表[人]的Person类和代表部门的Department类来看:
如果客户希望知道某人的经理是谁,他必须先取得这个人所属的部门Department对象:
manager = onePerson.Department.Manager;
这样的代码就对客户揭露了Department的工作原理,于是客户知道:Department用以追踪[经理]这条信息。我们可以在Person中建立一个简单的委托函数,对客户隐藏Department。
public Person GetManager()
{
return department.Manager;
}
这样,客户希望知道某人的经理是谁,就可以这样调用:
manaer = onePerson.GetManager();
只要完成了对Department所有函数的委托关系,并相应修改了Person的所有客户,就可以移除Person中的Department访问属性。
某个class做了过多的简单委托动作(simple delegation),让客户直接调用delegate(委托类)。
在Hide Delegate中,我们隐藏了delegate object,这层封装虽然使得客户只关心server object类,但同时它也付出了一定代价:每当客户要使用delegate(受托类)的新特性时,你就必须在server端添加一个简单委托函数。随着delegate的特性(功能)越来越多,这一过程会让你痛苦不已。Server完全变成了一个[中间人],此时你就应该让客户直接调用delegate。
如Hide Delegate中范例所示,做一个反向过程。
什么程序的隐藏才是合适的,这很难说,你可以在系统运行过程中不断进行调整,随着系统的变化,[合适的隐藏度]这个尺度也相应改变。
你所使用的server class需要一个额外函数,但你无法修改这个class;这时可以在client class中建立一个函数,并以一个server class实体作为第一引数(argument)。
你使用一个免费dll,它真得不错,为你提供了你想要的所有服务,而后,你又需要一个新服务,这个class却无法供应,如果这个dll是开源的,你有源码,那么你可以自行添加一个你想要的新函数,如果不是开源的,你就得在客户端编码,补足你要的那个函数。
假如我需要跨过一个收费周期(billing period),原本代码像这样:
Date newStart = new Date(previousEnd.getYear(),
previousEnd.getMonth(),previousEnd.getDate() + 1);
我可以通过添加一个外加函数:
Date newStart = nextDay(previousEnd);
private Date nextDay(Date arg)
{
return new Date(arg.getYear(),arg.getMonth(),arg.getDate() + 1);
}
外加函数终归是权宜之计,如果有可能,仍然应该将这些函数搬移到它们的理想家园。
你所使用的server class需要一些额外函数,但你无法修改这个class。
建立一个新class,使它包含这些额外函数,让这个扩展品成为source class的subclass(子类)或wrapper(外覆类)。
对于Introduce Foreign Method来说,如果你需要的外加函数超过两个,就很难控制住它们了,所以,你需要将这些函数组织在一起,放到一个恰当地方去。
所谓local extension,是一个独立的class,但也是其extended class的subtype(不同于subclass,它和extended class并不一定存在严格的继承关系,只要能够提供extended class的所有特性即可)。