本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/44958839
在上一篇文章中介绍了“引入外加函数”。本文将介绍“引入本地扩展”这种重构手法。
下面让我们来学习这种重构手法吧。
发现:你需要为服务类提供一些额外函数,但你无法修改这个类。
解决:建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或者包装类。
我们都无法预知一个类的未来,它们常常无法为你预先准备一些有用的函数。如果可以修改源码,那就太好了,那样就可以直接加入自己需要的函数。但是你经常无法修改源码。如果只是需要一两个函数,可以引入外加函数进行处理。但如果需要多个函数,外加函数就很难控制它们了。所以,需要将这这些函数组织起来,放到一个恰当的地方去。要达到这样的目的,需要用到子类化和包装这两种技术。这种情况下,把子类或包装类统称为本地扩展。
本地扩展是一个独立的类,但也是被扩展的子类型:它提供类的一切资源特性,同时额外添加新特性。在任何使用源类的地方,你都可以使用本地扩展取而代之。
使用本地扩展使得以坚持“函数和数据应该被统一封装”的原则。如果你一直把本该放在扩展类中的代码零散地放置于其它类中,最终只会让其它类变得复杂,并使得其中函数难以被复用。
在子类和包装类之间做选择,通常会选择子类,因为这样的工作量比较小。但是,制作子类的最大障碍在于,它必须在对象创建初期实施。如果可以接管对象的创建过程,那当然没问题;但如果你想在对象创建之后再使用本地扩展,就会有问题。此外,子类化方案还必须产生一个子类对象,这样如果有其它对象引用了旧对象,就同时有两个对象保存了原数据!如果原数据不可修改,那可以放心复制;但是如果允许修改,问题就随之而来,因为一个修改动作无法同时改变两份副本。这时就必须改用包装类。使用包装类时,对本地扩展的修改会波及原对象,反之也成立。
class MyDateSub extends Date{ public MyDateSub nextDay()... public int dayOfYear()... }包装类则需要用上委托:
class MyDateWrap{ private Date _original; }
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); }
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); }使用包装类有一个特殊问题:如何处理“接受原始类之实例为参数”的函数?
public boolean after(Date arg)
aWrapper.after(aDate); //can be made to work aWrapper.after(anotherWrapper); //can be made to work aDate.after(aWrapper); //not work这样覆写的目的是为了向用户隐藏包装类的存在。这是一个比较好的策略,因为包装类的用户的确不应该关心
public boolean equals(Date arg) //causes problems但是这样做很危险,尽管达到了我的目的,但JAVA系统的其它部分认为equals()符合交换律:如果a.equals(b)为真,那么b.equals(a)也必为真。违反这一规则将使我遭遇一大堆莫名其妙的错误。要避免这种情况,唯一的办法就是修改Date类。但是如果我修改Date类,又何必进行此项重构呢?所以,这种情况下,只能向用户公开“我进行了包装”这一事实。将以一个新函数来进行日期之间的相等性检查:
public boolean equalsDate(Date arg)
public boolean equalsDate(MyDateWrap arg)
原文地址:http://blog.csdn.net/pistolove/article/details/44958839