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

本文是在学习中的总结,欢迎转载但请注明出处: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:下一节将介绍重构笔记——重新组织函数)

重构笔记文章

重构笔记——入门篇

重构笔记——代码的坏味道(上

重构笔记——代码的坏味道(下)

重构笔记——构筑测试体

重构笔记——提炼函数

重构笔记——内联函数

重构笔记——内联临时变量

重构笔记——以查询取代临时变量

重构笔记——引入解释性变量

重构笔记——分解临时变量

重构笔记——移除对参数的赋值

重构笔记——以函数对象取代函数

重构笔记——替换算法

重构笔记——搬移函数

重构笔记——搬移字段

重构笔记——提炼类

重构笔记——将类内联化

重构笔记——隐藏"委托关系"

重构笔记——移除中间人

重构笔记——引入外加函数

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

时间: 2024-10-14 20:19:28

重构笔记——引入本地扩展的相关文章

重构改善既有代码设计--重构手法16:Introduce Foreign Method (引入外加函数)&& 重构手法17:Introduce Local Extension (引入本地扩展)

重构手法16:Introduce Foreign Method (引入外加函数)你需要为提供服务的类增加一个函数,但你无法修改这个类.在客户类中建立一个函数,并以第一参数形式传入一个服务类实例. 动机:这种事情发生了太多次了,你正在使用一个类,它真的很好,为你提供了需要的所有服务.而后,你又需要一项新服务,这个类却无法供应.于是你开始咒骂“为什么不能做这件事?”如果可以修改源码,你便可以自行添加一个新函数:如果不能,你就得在客户端编码,补足你要的那个函数. 如果客户类只使用这项功能一次,那么额外

重构笔记——提炼类

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/43059759         在上一篇文章中介绍了"搬移字段".本文将介绍"提炼类"这种重构手法.         下面让我们来学习这种重构手法吧. 开门见山         发现:某个类做了应该由两个类做的事. 解决:建立一个新类,将相关的字段和函数从旧类搬移到新类. 动机 我们或多或少听过这样的教诲:一个类应该是一个清楚的

重构笔记——隐藏“委托关系”

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/43769929         在上一篇文章中介绍了"将类内联化".本文将介绍"隐藏委托关系"这种重构手法.         下面让我们来学习这种重构手法吧. 开门见山         发现:客户通过一个委托关系来调用另一个对象. 解决:在服务类上建立客户所需的所有函数,用以隐藏委托关系. 动机 我们都知道,"封装&q

重构笔记——将类内联化

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/43159817         在上一篇文章中介绍了"提炼类".本文将介绍"将类内联化"这种重构手法.         下面让我们来学习这种重构手法吧. 开门见山         发现:某个类并没有做太多的事情. 解决:将这个类的所有特性搬移到另一个类中,然后移除原类. 动机 "将类内联化"正好与"

重构笔记——搬移字段

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/42780243         在上一篇文章中介绍了"搬移函数".本文将介绍"搬移字段"这种重构手法.         下面让我们来学习这种重构手法吧. 开门见山         发现:程序中某个字段被其所驻类之外的另一个类更多地用到. 解决:在目标类新建一个字段,修改原字段的所有用户,令它们改用新字段. 动机 在类之间移动状态

重构笔记——分解临时变量

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/42463871         在上一篇文章中介绍了"重构笔记--引入解释性变量".本文将介绍"分解临时变量"这种重构手法.         下面让我们来学习这种重构手法吧. 开门见山         发现:你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果. 解决:针对每次赋值,创造一个独立.对应的

重构笔记——移除对参数的赋值

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/42497857         在上一篇文章中介绍了"分解临时变值".本文将介绍"移除对参数的赋值"这种重构手法.         下面让我们来学习这种重构手法吧. 开门见山         发现:代码对一个参数进行赋值. 解决:以一个临时变量取代该参数的位置. //重构前 int dicount(int inputVal, i

重构笔记——搬移函数

本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/42679983         我们都知道,类往往因为承担过多的责任而变得臃肿不堪.这种情况下,一般会使用"提炼类"这种手法将一部分责任分离出去.如果一个类变得"不负责任",一般会使用"内联类"这种手法将它融入另一个类.如果一个类使用了另一个类,一般会运用"隐藏委托关系"手法将这种关系隐藏

iOS之在webView中引入本地html,image,js,css文件的方法 - sky//////////////////////////////////////ZZZZZZZZZZZZZZZ

iOS之在webView中引入本地html,image,js,css文件的方法 2014-12-08 20:00:16CSDN-sky_2016-点击数:10292 项目需求 最近开发的项目,需要一个webView,同时这个webView会需要引入一些项目中的资源: 一个本地的html文件,作为webView的模板 两张loading图片,在图片未加载的时候进行占位 jquery.js,scrollLoading.js 也是本地的,实现滚动加载图片功能 然后就开始了漫长的Google历程. 在w