在对象之间搬移特性
7.1 Move Method(搬移函数)
你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或将旧函数移除。
做法:
1. 检查源类中被源函数所使用的一切特性,考虑它们是否该被搬移。
2. 检查源类的子类和超类,看看是否有该函数的其他声明。
如果出现其他声明,你或许无法搬移,除非目标类也同样表现出多态性。
3. 在目标类中声明这个函数。(注意良好的命名)
4. 将源函数的代码复制到目标函数中。调整后者,使其能在新类中正常运行。
当需要使用源类的特性时:
4.1 将这个特性也移到目标类。
4.2 建立或使用一个从目标类到源类的引用关系。
4.3 将源对象当作参数传给目标函数。
4.4 如果所需特性是个变量,将它当作参数传给目标函数。
5. 决定如何从源函数正确引用目标对象。
6. 修改源函数,使之成为一个纯委托函数。
7. 决定是否删除源函数,或将它当作一个委托函数保留下来。
如果你经常要在源对象中引用目标函数,那么将源函数作为委托函数保留下来会比较简单。
7.2 Move Field(搬移字段)
你的程序中,某个字段被其所驻类之外的另一个类更多的用到。在目标类新建一个字段,修改源字段的所有用户,令它们改用新字段。
做法:
1. 在目标类建立与源字段相同的字段,并同时建立相应的取值/设值函数。
2. 决定如何在源对象中引用目标对象。(可以通过函数或目标对象字段)
3. 删除源字段。
4. 将所有对源字段的引用替换为对某个目标函数的引用。
7.3 Extract Class(提炼类)
某个类做了应该由两个类做的事。建立一个新类,将相关的字段和函数从旧类搬移到新类。
一个类应该是一个清楚的抽象,处理一些明确的责任。
动机:
1. 一个类含有大量的函数和数据,增加了类的理解难度。
2. 子类化只影响了类的部特性,或如果某些特性需要以一种方式来子类化,某些特性则需要以另一种方式子类化,这就意味着你需要分解原来的类。
做法:
1. 决定如何分解类所负的责任。
2. 建立一个新类,用以表现从旧类中分离出来的责任。
3. 建立“从旧类访问新类”的连接关系。
4. 对于你想搬移的每一个字段,以Move Field(7.2)搬移之。
5. 使用Move Method(7.1)将必要函数搬移到新类。先搬移较低层函数(也就是“被其他函数调用”多于“调用其他函数”),再搬移较高层函数。
6. 检查,精简每个类的接口。
7. 决定是否公开新类,如果你的确需要公开它,就要决定让它成为引用对象还是不可变的值对象。对于这种情况,
7.1 运行任何对象修改新对象的任何部分。这就使得新对象成为引用对象,可以考虑使用Change Value to Reference(8.3),这种情况下,旧类应该是新类的访问点。
7.2 不许任何人不通过旧类就修改新类。为此,可以将新类设置为不可修改的,或为它提供一个不可修改的接口。
7.3 将复制得到的新类对象传递给用户。
7.4 Inline Class(将类内联化)
某个类没有做太多事情。将这个类的所有特性搬移到另一个类中,移除原类。
做法:
1. 在目标类身上声明源类的public协议,并将其中所有函数委托至源类。
如果“以一个独立接口表示源类函数”更合适的话,就应该在内联之前使用Extract Interface(11.8)。
2. 修改所有源类引用点,改而引用目标类。
3. 运用Move Method(7.1)和Move Field(7.2),将源类的特性全部转移到目标类。
7.5 Hide Delegate(隐藏“委托关系”)
客户通过一个委托来调用另一个对象。在服务类上建立客户所需的所有函数,用以隐藏委托关系。
manager = john.getDepartment().getManager();
to
public Person getManager() {
return department.getManager();
}
manager = john.getManager();
动机:
如果某个客户先通过服务对象的字段得到另一个对象,然后调用后者的函数,那么客户就必须知晓这一层委托关系。如果委托关系发生变化,客户也得相应变化。使用隐藏委托关系,可以将这种变化限制在服务对象中,不会波及客户。
做法:
1. 对于每一个委托关系中的函数,在服务对象端建立一个简单的委托函数。
2. 调整客户,令它只调用服务对象提供的函数。
3. 每次调整后,编译并测试。
4. 如果将来不再有任何客户需要取用受托类,便可移除服务对象中的相关访问函数。
7.6 Remove Middle Man(移除中间人)
某个类做了过多的简单委托动作,让客户直接调用受托类。
public Person getManager() {
return department.getManager();
}
manager = john.getManager();
to
manager = john.getDepartment().getManager();
动机:
使用Hide Delegate所带来的代价是:每当客户要使用受托对象的新特性时,你就必须在服务端添加一个简单委托函数。具体的隐藏尺度只能自己把握。
做法:
1. 建立一个新函数,用以获得受托对象。
2. 对于每个受托函数,在服务类中删除该函数,并让需要调用该函数的客户转而调用受托对象。
7.7 Introduce Foreign Method(引入外加函数)
你需要为提供服务的类增加一个函数,但你无法修改这个类。在客户类中建立一个函数,并以第一参数形式传入一个服务类实例。
Date newStart = new Date(previousEnd.getYear(), previousEnd.getMonth(),previousEnd.getDate()+1);
to
Date newStart = nextDay(previousEnd);
private static Date nextDay(Date previousEnd) {
return new Date(previousEnd.getYear(), previousEnd.getMonth(),previousEnd.getDate()+1);
}
如果你发现自己为一个服务类创建了大量的外加函数,或者发现有许多类都需要同样的外加函数,就应该使用Introduce Local Extension(7.8)。
做法:
1. 在客户类中建立一个函数,用来提高你所需要的功能。
这个函数不应该调用客户类的任何特性。如果它需要一个值,把该值当作参数传给它。
2. 以服务类实例作为该函数的第一个参数。
3. 将该函数注释为:“外加函数,应在服务类中实现。”
7.8 Introduce Local Extension(引入本地扩展)
你需要 为服务类提供一些额外函数,但你无法修改这个类。建立一个新类,使它包含这些额外函数,让这个扩展品成为源类的子类或包装类。
做法:
1. 建立一个扩展类,将它作为原始类的子类或包装类。
2. 在扩展类中加入转型构造函数。
如果采用子类化方案,那么转型构造函数应该调用适当的超类构造函数;如果采用包装类方案,那么转型构造函数应该将它得到的传入参数以实例变量的形式保存起来,用以接收委托的原对象。
3. 在扩展类中加入新特性。
4. 根据需要,将原对象替换为扩展对象。
5. 将针对原始类定义的所有外加函数搬移到扩展类中。
子类化方案中,一般不要在扩展类中覆写原始类的函数,最好添加新函数;包装类方案中,可以通过重载函数,分别接收原始类对象为参数和包装类对象为参数,这样就不必检查未知对象的类型。