在对象之间搬移特性(Moving features Between Objects)

 

在对象的设计过程中,[决定把责任放在哪儿]即使不是最重要的事,也是最重要的事之一。

1 Move Method(搬移函数)

概述

你的程序中,有个函数与其所驻class之外的另一个class进行更多交流:调用后者,或被后者调用。在该函数最常引用(指涉)的class中建立一个有着类似行为的新函数,将旧函数变成一个单纯的委托函数(delegating method),或是将旧函数完全移除;

动机

[函数搬移]是重构理论的支柱。

如果一个class有太多行为,或如果一个class与另一个class有太多合作而形成高度耦合(highly coupled),就应该搬移函数;

是否应该移动一个函数,这往往不是一个容易做出的决定,如果不能肯定是否应该移动一个函数,那么或许[移动这个函数与否]并不是那么重要。

范例

我们用一个表示[账户]的class来说明这项重构:

Code


假设有数种账户,每一种都有自己的[透支金计费规则]。所以透支金计费方法我们不应该放在账户里,我们希望能够把透支金计费方法overdraftCharge()放到AccountType类中。

Code


上面,我们把透支金计费方法移到了账户类型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 classsource class的引用;

3 source class当作参数传给target method

4 如果所需特性是个变量,将它作为参数传给target method

 

很明显,上述用的是第四种。具体用哪种看实际情况。

2 Move Field(搬移值域)

概述

你的程序中,某个field被其所驻class之外的另一个class更多地用到。那么在target class中建立一个new field,修改source field的所有用户,令它们必胜new field

动机

classes之间移动状态(states)和行为,是重构过程中必不可少的措施。如果对于一个field(值域),在其所驻class之外的另一个class中有更多函数使用了它,就可以考虑搬移这个field。所谓[使用]可能是通过设值/取值(setting/getting)函数间接进行。

范例

下面是Account class的部分代码:

Code

 

每种账户类型的利率不同,所以我想把表示利率的_interestRate搬移到AccountType中去。

Code

 

3 Extract Class(提炼类)

概述

某个class做了应该由两个classes做的事,建立一个新class,将相关的值域和函数从旧class搬移到新class

动机

一个class应该是一个清楚的抽象(abstract),处理一些明确的责任。

 

在实际工作中,class会不断成长扩展。你会在这加入一些功能,在那加入一些数据,给某个class添加一项新责任时,你会觉得不值得为这项责任分离出一个单独的class。于是,随着责任的不断增加,这个class会变得过份复杂。

 

如果某些数据和某些函数总是一起出现,如果某些数据经常同时变化甚至彼此相依,这就表示你应该将它们分离出去。

范例

从一个简单的Person class开始:

Code

 

这上面例子中,我们可以将[与电话号码相关]的行为分离到一个独立class中,如下所示:

Code

 

4 Inline Class(将类内联化)

概述

正好与Extract class相反,如果你的某个class没有做太多事情(没有承担足够责任),就可以将class的所有特性搬移到另一个class中,然后移除原class

动机

如果一个class不再承担足够的责任、不再有单独存在的理由,就应该挑选这一[萎缩class]的最频繁的用户(也是个class),以Inline Class手法将[萎缩class]塞进去。

范例

Extract Class范例所示,可以将TelephoneNumber塞回到Person类去。

5 Hide Delegate(隐藏[委托关系])

概述

对于客户直接调用其server object(服务对象)的delegate class,在server端(某个class)建立客户所需的所有函数,用以隐藏委托关系(delegation)

动机

[封装]即使不是对象的最关键特征,也是最关键特征之一。[封装]意味着每个对象都应该尽可能少了解系统的其他部分。如此一来,一旦发生了变化,需要了解这一变化的对象就会比较少,这会使得变化比较容易进行。

 

如果某个客户调用了[建立于server object的某个值域基础之上]的函数,那么客户就必须知道这一委托对象(即server object的特殊值域)。万一委托关系发生变化,客户也得相应变化。你可以在server端放置一个简单的委托函数(delegate method),将委托关系隐藏起来,从而去除这种依存性。这么一来即使将来发生委托关系的变化,变化将被限制在server中,不会波及客户。

范例

我们从代表[人]的Person类和代表部门的Department类来看:

Code

 

如果客户希望知道某人的经理是谁,他必须先取得这个人所属的部门Department对象:

manager = onePerson.Department.Manager;

这样的代码就对客户揭露了Department的工作原理,于是客户知道:Department用以追踪[经理]这条信息。我们可以在Person中建立一个简单的委托函数,对客户隐藏Department。

    public Person GetManager()

    {

        return department.Manager;

}

这样,客户希望知道某人的经理是谁,就可以这样调用:

manaer = onePerson.GetManager();

只要完成了对Department所有函数的委托关系,并相应修改了Person的所有客户,就可以移除Person中的Department访问属性。

6 Remove Middle Man(移除中间人)

概述

某个class做了过多的简单委托动作(simple delegation),让客户直接调用delegate(委托类)

动机

Hide Delegate中,我们隐藏了delegate object,这层封装虽然使得客户只关心server object类,但同时它也付出了一定代价:每当客户要使用delegate(受托类)的新特性时,你就必须在server端添加一个简单委托函数。随着delegate的特性(功能)越来越多,这一过程会让你痛苦不已。Server完全变成了一个[中间人],此时你就应该让客户直接调用delegate

范例

Hide Delegate中范例所示,做一个反向过程。

备注

什么程序的隐藏才是合适的,这很难说,你可以在系统运行过程中不断进行调整,随着系统的变化,[合适的隐藏度]这个尺度也相应改变。

7 Introduce Foreign Method(引入外加函数)

概述

你所使用的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);

}

备注

外加函数终归是权宜之计,如果有可能,仍然应该将这些函数搬移到它们的理想家园。

8 Introduce Local Extension(引入本地扩展)

概述

你所使用的server class需要一些额外函数,但你无法修改这个class

建立一个新class,使它包含这些额外函数,让这个扩展品成为source classsubclass(子类)或wrapper(外覆类)。

动机

对于Introduce Foreign Method来说,如果你需要的外加函数超过两个,就很难控制住它们了,所以,你需要将这些函数组织在一起,放到一个恰当地方去。

所谓local extension,是一个独立的class,但也是其extended classsubtype(不同于subclass,它和extended class并不一定存在严格的继承关系,只要能够提供extended class的所有特性即可)。

范例:使用Subclass(子类)

public class MyDateSub : Date
{
    
private Date nextDay()
    {
        
return new Date(getYear(),getMonth(),getDate() + 1);
    }
}

 

范例:使用Wrapper(外覆类)

Code

 

posted @ 2009-09-19 00:49  xiaotou745  阅读(419)  评论(1编辑  收藏  举报