重构改善既有代码的设计《七》在对象之间搬移特性
7.1Move Method (搬移函数)
你的程序中,有个函数与其所驻类之外的另一个类有过多的交流,调用后者,或者被后者调用
在该函数最常引用的类中建立一个有类似行为的新函数,将旧函数单纯的变为一个委托函数或者将旧函数完全删除
1,动机
重构理论的支柱:搬移函数
如果一个类有太多行为,或者一个类和另外一个类因为太多合作而形成高度耦合的关系,就应该搬移函数,通过这种手段可以使系统的类更加简单
2,做法
- 检查原类中被源函数所使用的一切特性(包括字段和函数)考虑他们是否也该搬移
如果某一个特性z只被你打算搬移的那个函数用到,就应该将它一并搬移,如果有其他的函数也用到这个特性,可以考虑将使用该特性的所有函数一块搬移 - 检查源类的子类和超类,看看是否有该函数的其他声明
如果出现了其他声明,你或许无法搬移,除非目标类也同样表现出多态性 - 在目标类声明这个函数
- 将源函数代码复制到目标函数,调整后者,使其能在新家正常运行,
如果目标函数使用源类的特性,你得绝定如何从目标函数引用源对象,如果目标类没有相应的引用机制,就把源对象的引用当作参数,传给新建立的目标函数
如果源函数包含异常处理,你得判断逻辑上应该由那个类处理,如果由源类处理,就把异常留在原地 - 编译目标类
- 决定如何从源函数正确引用目标对象,
可能会有一个现成的字段和函数帮你取得目标对象,如果没有看能否建立一个函数,如果不行就需要在源类中新建一个字段来保存目标对象 - 修改源函数,使之成为纯委托函数
- 编译,测试
- 决定是否删除源函数,或将作为一个委托函数保留下来
- 如果要移除函数,请将源类中对源函数的所有的调用,替换为目标函数的调用
7.2 Move Field(搬移字段)
在你的程序中,某一个字段被其所驻类之外的另外一类更多的用到
在目标类新建一个字段,修改源字段的所有用户,令它们改用新字段
1,动机
在类之间移动状态和行为,是重构过程中必不可少的措施
7.3Extract Class(提炼类)
某一个类做了应该由两个类做的事
建立一个新类,将相关的字段和函数从旧类搬移到新类
7.4Inline Class(将类内联化)
某个类没有做太多的事情
将这个类的所有特性搬移到另一个类中,然后移除源类
7.5 Hide Delegate(隐藏委托关系)
客户通过一个委托类来调用另一个对象
在服务类上建立客户所需的所有函数,用以隐藏委托关系
1,动机
如果某一个客户通过服务对象的字段得到另一个对象,然后调用后者的函数,那么客户就必须知晓这一层委托关系。
你可以通过服务对象放置一个简单的委托函数,将委托关系隐藏起来,从而去除这种依赖,这么以来,即便委托关系发生变化,变化也将限制在服务对象中,不会涉及客户
2,做法
- 对于每一委托关系中的函数,在服务对象建立一个简单的委托函数
- 调整客户,令他们只调用服务对象提供的函数,
- 编译测试
3,举例
public class Person {
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
Department department;
}
public class Department {
private String chargeCode;
private Person manager;
public Department(Person manager){
this.manager=manager;
}
public Person getManager(){
return manager;
}
}
Person修改为
public class Person {
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public Person getManager(){
return department.getManager();
}
Department department;
}
join.getDepartment().getManager()就可以变为join.getManager()
7.6Remove Middle Man(移除中间人)
某个类做了过多的简单委托动作
让客户直接调用委托类
1,动机
与7.5相反
7.5代价:每当客户使用委托类的新特性的时候,就必须在服务端加一个简单委托函数,随着受托类的特性功能越来越多,这一过程很复杂,服务类完全变成一个中间人
7.7Introduce Foreign Method(引入外加函数)
你需要为提供服务的类增加一个函数,但是你无法修改这个类
在客户类中建立一函数,并以第一参数的形式传入一个服务类实例
Date newsStart=new Date(previousEnd.getYear(),previousEnd.getMonth(),previousEnd.getDay()+1);
修改为
Date newsStart=nextDay(previousEnd);
private Date nextDay(Date args){
return new Date(args.getYear(),args.getMonth(),args.getDay()+1);
}
1,做法
- 在客户类建立一个函数,用来提供你需要的功能
这个函数不应该调用客户类的任何特性,如果他需要一个值,就把该值作为参数传给它 - 以服务类实例作为该函数第一个参数
- 将该函数注释为外加函数应该在服务类实现
7.8Introduce Local Extention(引入本地扩展)
你需要为服务类提供一下额外函数,但你无法修改这个类
建立一个新类,使它包含这些额外函数,让这个扩展品成为源类的子类或包装类
1,动机
需要的额外函数超过两个,两种方式
子类化,包装(统称为本地扩展)
本地扩展原则:函数和数据统一封装
子类的最大障碍:必须对象创建期实施。如果想在对象创建之后再使用本地扩展,就有问题,此外子类化方案还必须产生一个子类对象
2,做法
- 建立一个扩展类,将他作为原始类的子类或者包装类
- 再扩展类中加入转型构造函数
转型构造函数:接受原对象作为参数的构造函数,如果使用子类化那么转型构造函数应该调用适当的超类作为构造函数,如果使包装类
那么转型构造函数应该将它得到的传入参数以实例变量的形式保存起来,用作接受委托的原对象 - 在扩展类加入新特性
- 根据需要将原对象替换为扩展对象
- 将针对原始类定义的所有外加函数搬移到扩展类中
待决定事项,使用子类还是包装类
class mfDateSub extends Date{
//转型构造函数
public mfDateSub (Date arg){
super (arg.getTime());
}
public mfDateSub nextDay()...
public int dayOfYear()...
}
class mfDateSub {
public Date original;
//转型构造函数
public mfDateSub (Date arg){
original=arg;
}
public mfDateSub nextDay()...
public int dayOfYear()...
}
3,问题
包装类如何处理“接受原始类之实例为参数”的函数?(一般覆写源函数会出现)
列如public boolean after(Date arg)
由于无法改变原始类
解决:包装类的after函数可以接受包装类或原始类对象,原始类after()函数只能接受原始类对象
aWrapper.after(aDate)
aWrapper.after(anotherWrapper)
aDate.after(aWrapper)
推荐阅读
-
《重构-改善既有代码的设计》重构手法(搬移特性)
-
PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性_PHP
-
PHP杂谈《重构-改善既有代码的设计》之对象之间搬移特性
-
acdsee 2009 许可证代码 PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性
-
acdsee 2009 许可证代码 PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性
-
重构改善既有代码的设计《七》在对象之间搬移特性
-
PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性_PHP教程
-
PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性
-
PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性_php技巧
-
PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性_php技巧