你需要为服务类提供一些额外函数,但你无法修改这个类。建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或包装类。
在使用一些无法修改源码的类时,需要添加一些原类没有提供的函数,而这些函数又比较多,那么我们需要将这些函数组织在一起放到恰当的地方。要达到这一目的,有两种标准对象技术可以用—子类化(subclassing)和包装(wrapping),这种情况下,我们把子类和包装类统称为本地扩展(local extension)。
使用本地扩展,使得“函数和数据应该被统一封装”这一原则得以坚持。如果一直把这些本该放在扩展类中的代码零散的置于其他函数中,最终只会让其他这些类变得过分复杂,并是的其中的函数难以复用。
在子类和包装类之间做选择时,首选子类,因为这样的工作量比较少。使用子类最大的障碍在于,它必须在对象创建期实施。如果我们可以接管对象创建过程,那当然没问题;但如果想在对象创建之后再使用本地扩展,就有问题了。此外,子类化方案还必须产生一个子类对象,这种情况下,如果有其他对象引用了旧对象,我们就同时有两个对象保存了原数据!如果原数据是不可修改的,那也没问题,我们可以放心进行复制;但如果原数据允许被修改,问题就来了,因为一个修改动作无法同时改变两份副本。这是我们就必须改用包装类。使用包装类时,对本地扩展的修改会搏击原对象,反之亦然。
1.建立一个扩展类,将它作为原始类的子类或包装类。
2.在扩展类中加入转型构造函数。
所谓“转型构造函数”是指“接受原对象作为参数”的构造函数。如果采用子类化方案,那么转型构造函数应该调用适当的超类构造函数;如果采用包装类方案,那么转型构造函数应该将它得到的传入参数以实例变量的形式保存起来,用作接受委托的原对象。
3.在扩展类中加入新特性。
4.根据需要,将原对象替换为扩展对象。
5.将针对原始类定义的所有外加函数搬移到扩展类中。
以java1.0.1的Date类为例。第一项待解决事项就是:使用子类还是包装类。子类化是比较显而易见的办法:
class myDateSub extends Date{ public myDateSub nextDay() ... public int dayOfYear() ... }
包装类则需用上委托:
class myDateWrap{ private Date original; }范例1:使用子类
首先,建立一个myDateSub类表示“日期”,并使其成为Date的子类:
class myDateSub extends Date
然后,处理Date和扩展类的不同处。MyDateSub构造函数需要委托给Date构造函数:
public MyDateSub(String dateString) { super(dateString); }
现在,加入一个转型构造函数,其参数是一个源类对象:
public MyDateSub(Date arg) { super(arg.getTime); }
现在,在扩展类中添加新特性,并使用Move Method将所有外加函数搬移到扩展类。于是下面代码:
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 dateString) { original = new Date(dateString); }
而转型构造函数则只是对其实例变量赋值而已:
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.equale(other.original)); }
完成这项工作后,就可以使用Move Method将日期相关行为搬移到新类中。于是一下代码:
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); }
使用包装类有一个特殊问题:如何处理“接受原始类之实例为参数”的函数。例如:
public boolean after(Date arg)
由于无法改变原始类,所以我们只能做到在一个方向上的兼容--包装类的after() 函数可以接受包装类或原始类的对象;但原始类只能接受原始类的对象,不接受包装类对象。
这样覆写的目的是为了向用户隐藏包装类的存在。但是我们无法完全隐藏包装类的存在,因为某些系统所提供的函数(例如equals())会出问题。
在这种情况下,我们只能向用户公开“我进行了包装”这一事实。我们可以以一个新函数来进行日期之间的相等性检查:
public boolean equalsDate(Date arg);
可以重载equalsDate(),让一个重载版本接受Date对象,另一个重载版本接受MyDateWrap对象。这样就不必检查未知对象的类型了:
public boolean quealsDate(MyDateWrap arg);
子类化方案中就没有这样的问题,只要不覆写源函数就行了。