重构对象-Introduce Local Extension引入本地扩展八
重构对象-Introduce Local Extension引入本地扩展八
1.引入本地扩展
1.1.使用场景
当我们调用第三方服务的类时,通常这个类我们都无法修改它,如果想要让他添加一个函数实现我们需要的功能这个时候可以使用Introduce Foreign Method引入外加函数
, 但如果你需要的额外函数超过两个,外加函数(foreign methods)就很难控制住它 们了。所以,你需要将这些函数组织在一起,放到一个恰当地方去。要达到这一目 的,标准对象技术subclassing和wrapping是显而易见的办法。这种情况下我把subclass 或wrapper称为local extention(本地扩展〕
1.2.如何使用
- 建立一个扩展类,将它作为原始类的子类或包装类。
- 在扩展类中加入转型构造函数。
- 所谓“转型构造函数”是指“接受原对象作为参数”的构造函数。
- 如果采用子类化方案,那么转型构造函数应该调用适当的超类构造函数;如果采用包装类方案,那么转型构造函数应该将它得到的传入参数以实例变量的形式保存起来,用作接受委托的原对象。
- 在扩展类中加入新特性。
- 根据需要,将原对象替换为扩展对象。
- 将针对原始类定义的所有外加函数搬移到扩展类中。
1.3.示例
我将以Java 1.0.1的Date class为例。Java 1.1已经提供了我想要的功能,但是在它到来之前的那段日子,很多时候我需要扩展Java 1.0.1的Date class。第一件待决事项就是使用subclass
或wrapper
。subclassing是比较显而易见的办法:
Class mfDate extends Date {
public nextDay()...
public dayOfYear()...
wrapper则需要用上委托(delegation):
class mfDate {
private Date _original;
1.Subclass方式
首先,我要建立一个新的MfDateSub class来表示日期
class MfDateSub extends Date
然后,我需要处理Date 和我的extension class之间的不同处。MfDateSub 构造函数需要委托(delegating)给Date构造函数:
public MfDateSub (String dateString) {
super (dateString);
};
现在,我需要加入一个转型构造函数,其参数是一个隶属原类的对象:
public MfDateSub (Date arg) {
super (arg.getTime());
}
现在,我可以在extension class中添加新特性,并使用Move Method 将所有外加函数(foreign methods)搬移到extension class。于是,下面的代码:
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 MfDate...
Date nextDay() {
return new Date (getYear(),getMonth(), getDate() + 1);
}
2.wrapper方式
首先声明一个wrapping class:
class mfDate {
private Date _original;
}
使用wrapping方案时,我对构造函数的设定与先前有所不同。现在的构造函数将只是执行一个单纯的委托动作(delegation):
public MfDateWrap (String dateString) {
_original = new Date(dateString);
};
而转型构造函数则只是对其instance变量赋值而己:
public MfDateWrap (Date arg) {
_original = arg;
}
接下来是一项枯燥乏味的工作:为原始类的所有函数提供委托函数。我只展示两个函数,其他函数的处理依此类推。
public int getYear() {
return _original.getYear();
}
public boolean equals (MfDateWrap arg) {
return (toDate().equals(arg.toDate()));
}
完成这项工作之后,我就可以后使用Move Method 将日期相关行为搬移到新class中。于是以下代码:
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 MfDate...
Date nextDay() {
return new Date (getYear(),getMonth(), getDate() + 1);
}
使用wrappers有一个特殊问题:如何处理「接受原始类之实体为参数」的函数?例如:public boolean after (Date arg)由于无法改变原始类〔original),所以我只能以一种方式使用上述的after() :
aWrapper.after(aDate) // can be made to work
aWrapper.after(anotherWrapper) // can be made to work
aDate.after(aWrapper) // will not work
这样覆写(overridden)的目的是为了向用户隐藏wrapper 的存在。这是一个好策略,因为wrapper 的用户的确不应该关心wrapper 的存在,的确应该可以同样地对待wrapper(外覆类)和orignal((原始类)。但是我无法完全隐藏此一信息,因为某些系统所提供的函数(例如equals() 会出问题。你可能会认为:你可以在MfDateWrap class 中覆写equals(),像这样:
public boolean equals (Date arg) // causes problems
但这样做是危险的,因为尽管我达到了自己的目的,Java 系统的其他部分都认为equals() 符合交换律:如果a.equals(b)为真,那么b.equals(a)也必为真。违反这一规则将使我遭遇一大堆莫名其妙的错误。要避免这样的尴尬境地,惟一办法就是修改Date class。但如果我能够修改Date ,我又何必进行此项重构?所以,在这种情况下,我只能(必须〕向用户暴露「我进行了包装」这一事实。我将以一个新函数来进行日期之间的相等性检查(equality tests):
public boolean equalsDate (Date arg)
我可以重载equalsDate() ,让一个重载版本接受Date 对象,另一个重载版本接受MfDateWrap 对象。这样我就不必检查未知对象的型别了:
public boolean equalsDate (MfDateWrap arg)
subclassing方案中就没有这样的问题,只要我不覆写原函数就行了。但如果我覆写了original class 中的函数,那么寻找函数时,我会被搞得晕头转向。一般来说,我不会在extension class 中覆写0original class 的函数,我只会添加新函数。