重构笔记——引入本地扩展

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/44958839


        在上一篇文章中介绍了引入外加函数。本文将介绍“引入本地扩展”这种重构手法。

        下面让我们来学习这种重构手法吧。



开门见山


        发现:你需要为服务类提供一些额外函数,但你无法修改这个类。

        解决:建立一个新类,使它包含这些额外函数。让这个扩展品成为源类的子类或者包装类。



动机


        我们都无法预知一个类的未来,它们常常无法为你预先准备一些有用的函数。如果可以修改源码,那就太好了,那样就可以直接加入自己需要的函数。但是你经常无法修改源码。如果只是需要一两个函数,可以引入外加函数进行处理。但如果需要多个函数,外加函数就很难控制它们了。所以,需要将这这些函数组织起来,放到一个恰当的地方去。要达到这样的目的,需要用到子类化和包装这两种技术。这种情况下,把子类或包装类统称为本地扩展。

        本地扩展是一个独立的类,但也是被扩展的子类型:它提供类的一切资源特性,同时额外添加新特性。在任何使用源类的地方,你都可以使用本地扩展取而代之。

        使用本地扩展使得以坚持“函数和数据应该被统一封装”的原则。如果你一直把本该放在扩展类中的代码零散地放置于其它类中,最终只会让其它类变得复杂,并使得其中函数难以被复用。

        在子类和包装类之间做选择,通常会选择子类,因为这样的工作量比较小。但是,制作子类的最大障碍在于,它必须在对象创建初期实施。如果可以接管对象的创建过程,那当然没问题;但如果你想在对象创建之后再使用本地扩展,就会有问题。此外,子类化方案还必须产生一个子类对象,这样如果有其它对象引用了旧对象,就同时有两个对象保存了原数据!如果原数据不可修改,那可以放心复制;但是如果允许修改,问题就随之而来,因为一个修改动作无法同时改变两份副本。这时就必须改用包装类。使用包装类时,对本地扩展的修改会波及原对象,反之也成立。




做法


(1)建立一个扩展类,将它作为原始类的子类或者包装类。
(2)在扩展类中加入转型构造函数。(所谓“转型构造函数”是指“接受原对象作为参数”的构造函数)
(3)在扩展类中加入新特性。
(4)根据需要,将原始对象替换为扩展对象。
(5)将针对原始类定义的所有外加函数版移到扩展类中。


示例


        我们以JAVA中的Date类为例。Java已经提供了我们想要的功能,但是在到来之前,很多时候需要扩展Date类。
        第一件需要做的事情就是:使用子类还是包装类。子类化是比较显而易见的方法:
class MyDateSub extends Date{
	public MyDateSub nextDay()...
	public int dayOfYear()...
}
        包装类则需要用上委托:
class MyDateWrap{
	private Date _original;
	
}
        
范例:使用子类
        首先,要建立一个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);
}


范例:使用包装类
        首先声明一个包装类,使用包装类时,对构造函数的设定与先前有所不同。现在的构造函数将只执行一个单纯的委托动作:
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)
        由于无法改变原始类,所以我只能做到在一个方向上的兼容——包装类的after()函数可以接受包装类或原始类的对象;但原始类的after()函数只能接受原始类对象,不接受包装类对象:
aWrapper.after(aDate); //can be made to work
aWrapper.after(anotherWrapper); //can be made to work
aDate.after(aWrapper); //not work
        这样覆写的目的是为了向用户隐藏包装类的存在。这是一个比较好的策略,因为包装类的用户的确不应该关心
包装类的存在,的确应该可以同样地对待包装类和原始类。但是我无法隐藏包装类的存在,因为某些系统提供的函数
(例如equals())会出问题的。可能你会认为:在MyDateWrap类中覆写equals(),像这样:
public boolean equals(Date arg) //causes problems
         但是这样做很危险,尽管达到了我的目的,但JAVA系统的其它部分认为equals()符合交换律:如果a.equals(b)为真,那么b.equals(a)也必为真。违反这一规则将使我遭遇一大堆莫名其妙的错误。要避免这种情况,唯一的办法就是修改Date类。但是如果我修改Date类,又何必进行此项重构呢?所以,这种情况下,只能向用户公开“我进行了包装”这一事实。将以一个新函数来进行日期之间的相等性检查:
public boolean equalsDate(Date arg)      
        可以重载equalsDate(),让一个重载版本接受Date对象,另一个重载版本接受MyDateWrap对象。这样就不必要检查未知对象类型了:
public boolean equalsDate(MyDateWrap arg)   
        子类化方案中就没有这样的问题,只要不覆写原函数就行。但如果覆写原始类中的函数,那么寻找函数时,会被搞的晕头转向的。一般不会再扩展类中覆写原始类的函数,只会添加新函数


        本文主要介绍了重构手法——引入本地扩展。该手法比较简单,很容易就能够理解,这里就不累赘了。
        最后,希望本文对你有所帮助。有问题可以留言,谢谢。(PS:下一节将介绍重构笔记——重新组织函数)



重构笔记文章





  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值