重构——在对象之间搬移特性(2)

Inline Class

某个类并没有做太多的事情。应该将这个类的所有特性搬移到另一个类中,然后移除原类。

过程与Extract Class相反,不再做介绍。

Hide Delegate

客户通过一个委托关系来调用另一个对象。应当在服务类上建立客户所需的所有函数,用以隐藏委托关系。
重构——在对象之间搬移特性(2)
动机
我们都知道,”封装“即使不是对象的最为关键的特征,也是最为关键的特征之一。 ”封装“意味着每个对象都应该尽可能少了解系统的其它部分。这样,一旦发生变化,需要了解这一变化的对象就会比较少——这会使变化比较容易。
如果某个客户先通过服务对象的字段得到了另一个对象,然后调用后者的函数,那么客户就必须知晓这一层委托关系。万一委托关系发生了变化,那么客户也得相应改变。那么,我们可以再服务器对象上放置一个简单的委托函数,将委托关系隐藏起来,从而去除这种依赖。这样,即使在将来委托关系发生变化,也只在服务器对象中,而不会涉及客户。 简而言之,就是多加一个中介。
重构——在对象之间搬移特性(2)
范例

class Person{
	Department _department;
 
	public Department get_department() {
		return _department;
	}
	public void set_department(Department _department) {
		this._department = _department;
	}
}
class Department{
	private String _changeCode;
	private Person _manager;
	
	public Person get_manager() {
		return _manager;
	}
	public void set_manager(Person _manager) {
		this._manager = _manager;
	}
	//......
}

如果客户想要知道某人的经理是谁,他必须先取得Department对象:

		Person john = new Person();
		Person manager = john.get_department().get_manager();

这样的编码对客户暴露了Department的工作原理,于是客户知道;Department用以追踪“经理”这条信息。如果对客户隐藏Department,可以减少耦合。为了到达预期的目的,在Person中建立一个简单的委托函数:

public Person getManager(){
		return _department.get_manager();
	}

现在,需要修改Person的所有用户,让它们使用新的函数:

Person manager = john.getManager();

Remove Middle Man

某个类做了过多的简单委托动作。应当让客户直接调用受托类。
与前者逆过程,不介绍。

Introduce Foreign Method

你需要为提供服务的类增加一个函数,但你无法修改这个类。应该在客户类中建立一个函数,并以第一参数形式传入一个服务器类实例。

//before
	Date newStart = new Date(previous.getYear(),
			previous.getMonth(),previous.getDate() + 1);

????

//after
	Date newStart = nextDay(previous);
	
	private static Date nextDay(Date date){
		//......
		return new Date(date.getYear(),date.getMonth(),date.getDate() + 1)
	}

动机
你经常会遇到这样的事情:你正在使用一个类,它很好,为你提供了很多需要的服务。而后,你又需要增加一项新服务,这个类却无法供应。于是你开始咒骂:为什么不能做这件事情呢?如果可以修改源码,你便可以自行增加一个新函数;如果不能,你就得在客户端编码,补足你要的那个函数。
如果只需要使用这个功能一次,那么额外的编码也没什么大不了的,甚至可能不需要原本提供服务的那个类。然而,如果需要多次使用这个函数,就不得不重复代码了。切记:重复代码是软件万恶之源。这些重复的代码应该被抽取出来放到同一个函数中。进行该项重构时,如果以外加函数实现一项功能,那就是一个明确信号:这个函数原本应该在提供服务的类中实现。
如果发现为一个服务类建立了大量外加函数,或者发现有许多类都需要同样的外加函数,就不应该再使用本项重构,而应该使用“引入本地扩展”。但是不要忘记:外加函数终归是权宜之计。如果有可能的话,应该将这些函数搬移到它们的理想家园。由于代码所有权的原因使你无法做这样的搬移,就把外加函数交给服务类,请它帮忙在服务类中实现这个函数。

Introduce Local Extension

你需要为服务类提供一些额外函数,但你无法修改这个类。应该建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或者包装类。
重构——在对象之间搬移特性(2)
动机
我们都无法预知一个类的未来,它们常常无法为你预先准备一些有用的函数。如果可以修改源码,那就太好了,那样就可以直接加入自己需要的函数。但是你经常无法修改源码。如果只是需要一两个函数,可以引入外加函数进行处理。但如果需要多个函数,外加函数就很难控制它们了。所以,需要将这这些函数组织起来,放到一个恰当的地方去。要达到这样的目的,需要用到子类化和包装这两种技术。这种情况下,把子类或包装类统称为本地扩展。
本地扩展是一个独立的类,但也是被扩展的子类型:它提供类的一切资源特性,同时额外添加新特性。在任何使用源类的地方,你都可以使用本地扩展取而代之。
使用本地扩展使得以坚持“函数和数据应该被统一封装”的原则。如果你一直把本该放在扩展类中的代码零散地放置于其它类中,最终只会让其它类变得复杂,并使得其中函数难以被复用。
在子类和包装类之间做选择,通常会选择子类,因为这样的工作量比较小。但是,制作子类的最大障碍在于,它必须在对象创建初期实施。如果可以接管对象的创建过程,那当然没问题;但如果你想在对象创建之后再使用本地扩展,就会有问题。此外,子类化方案还必须产生一个子类对象,这样如果有其它对象引用了旧对象,就同时有两个对象保存了原数据!如果原数据不可修改,那可以放心复制;但是如果允许修改,问题就随之而来,因为一个修改动作无法同时改变两份副本。这时就必须改用包装类。使用包装类时,对本地扩展的修改会波及原对象,反之也成立。
范例
我们以JAVA中的Date类为例。Java已经提供了我们想要的功能,但是在到来之前,很多时候需要扩展Date类。
第一件需要做的事情就是:使用子类还是包装类。子类化是比较显而易见的方法:

class MyDateSub extends Date{
	public MyDateSub nextDay()...
	public int dayOfYear()...
}

包装类则需要用上委托:

class MyDateWrap{
	private Date _original;
	
}     

1:使用子类
首先,要建立一个MfDateSub类来表示“日期”,并使其成为Date的子类:

 class MyDateSub extends Date

然后,需要处理Date和扩展类之间的不同处。MfDateSub构造函数需要委托给Date构造函数:

public MyDateSub(String dateStr){
	super(dateStr);
}

现在,需要加入一个转型构造函数,其参数是一个源类的对象:

public MyDateSub(Date arg){
	super(arg.getTime());
}

现在,可以再扩展类中添加新特性,并使用搬移函数将所有的外加函数搬移到扩展类。于是:

client class...
	private static Date nextDay(Date arg){
	// foreign method, should be on date
	return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1);
}

经过搬移之后,就变成:

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

2:使用包装类
首先声明一个包装类,使用包装类时,对构造函数的设定与先前有所不同。现在的构造函数将只执行一个单纯的委托动作:

class MyDateWrap{
	private Date _original;
}
public MyDateWrap(String dateStr){
	_original = new Date(dateStr);
}

而转型构造函数则只是对其实例变量赋值而已:

public MyDateWrap(Date arg){
	__original = arg;
}

接下来是一项枯燥乏味的工作:为原始类的所有函数提供委托函数。此处只展示两个函数:

public int getYear(){
	return original.getYear();
}
 
public boolean equals(Object arg){
	if(this==arg){
		return true;
	}
	if(!(arg instanceof MyDateWrap )){
		return false;
	}
	MyDateWrap other = (MyDateWrap)arg;
	return (_original.equals(other._original));
}

完成这项工作之后,可以使用搬移函数将日期相关行为搬移到新类中。于是:

client class...
	private static Date nextDay(Date arg){
	// foreign method, should be on date
	return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1);
}

经过搬移之后,有:

class MyDateWrap...
	Date nextDay(){
		return new Date(getYear(),getMonth(),getDate()+1);
}