首页 > 代码库 > 重构笔记——搬移函数

重构笔记——搬移函数

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


        我们都知道,类往往因为承担过多的责任而变得臃肿不堪。这种情况下,一般会使用"提炼类"这种手法将一部分责任分离出去。如果一个类变得"不负责任",一般会使用“内联类”这种手法将它融入另一个类。如果一个类使用了另一个类,一般会运用"隐藏委托关系"手法将这种关系隐藏起来通常是有帮助的。有时候隐藏委托关系会导致拥有者的接口经常性地变化,这时就可考虑使用"移除中间人"这种手法了。

        从本文开始将介绍“在对象之间搬移”系列的重构手法。本文将介绍“搬移函数”这种重构手法。

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


开门见山


        发现:程序中有个函数与其所驻类之外的另一个类进行更多交流。

        解决:在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。

技术分享

动机


        "搬移函数"是重构理论中比较重要的特性之一。一般情况下,如果一个类有太多的行为,或者如果一个类与另一个类有太多合作而形成高度耦合,这时候就应该搬移函数。通过这种手段,可以使系统中的类更简单,这些类最终也将更干净利落地实现系统交付的任务。

        在进行开发的过程中,我会时不时地浏览类的所有函数,从中寻找这样的函数:其使用另一个对象的次数要多于使用自己所驻对象的次数。一旦一些字段被移动,就应该进行这样的检查。一旦发现有可能搬移函数,就观察调用它的那一端、它调用的那一端,以及继承体系中的它的任何一个重定义的函数。然后根据“该函数与哪个读对象的交流比较多”来决定其移动路径。

        事情往往不是那么容易做出决定。如果不能肯定是否应该移动一个函数,就应该继续观察其它函数。移动其它函数往往会让这项决定变得容易一些。有时候,即使移动了其它函数,还是很难对当前的函数做出决定。如果真的很难做出决定,那么或许"移动该函数与否"并不重要。那就,就让它呆在那儿,反正以后总是可以修改的。


做法


(1)检查原类中被原函数所使用的一切特性,包括字段和函数,考虑它们是否也被搬移。
(2)检查原类的子类和超类,看看是否有该函数的其它声明。
(3)在目标类中声明这个函数。
(4)将原函数的代码复制到目标函数中。调整后者,使其能在新家中正常运行。
(5)编译目标类。
(6)决定应该如何从原函数正确引用目标对象。
(7)修改原函数,使之成为一个纯委托函数。
(8)编译,测试。
(9)决定是否删除原函数,或将它当作一个委托函数保留下来。
(10)如果要移除原函数,将原类中对原函数的所有调用,替换为对目标函数的调用。
(11)编译,测试。


示例


        我们从一个表示"账户"的Account类开始:
class Account {
	private AccountType _type;
	private int _daysOverdrawn;

	double overdraftCharge() {
		if (_type.isPremium()) {
			double result = 10;
			if (_daysOverdrawn > 7)
				result += (_daysOverdrawn - 7) * 0.85;
			return result;
		} else {
			return _daysOverdrawn * 1.75;
		}
	}
}
        现在假设有几种新的账户,每一种都有自己的“透支金额计费规则”,这样,我们就希望将overdraftCharge()函数搬移到AccountType类中。
        首先,需要观察被overdraftCharge() 使用的每一项特征,考虑是否值得将它们与overdraftCharge()函数一起移动。在此例中,我们将_daysOverdrawn字段保留在Account类中,因为这个值会随着不同种类的账户而发生变化。然后,将overdraftCharge()函数复制到AccountType中,并做相应调整。
class AccountType {
	double overdraftCharge(int daysOverdrawn) {
		if (isPremium()) {
			double result = 10;
			if (daysOverdrawn > 7)
				result += (daysOverdrawn - 7) * 0.85;
			return result;
		} else {
			return daysOverdrawn * 1.75;
		}
	}
}
        在例中,“调整”所表达的意思是:(1)对于使用AccountType特性的语句,去掉_type;(2)想办法得到仍然需要的Account类特性。当需要使用原类时,可有四种选择:(a)将这个特性同样移动到目标类,一般变为其成员变量;(b)建立或使用一个从目标类到原类的引用关系;(c)将原对象以参数传递给目标函数;(d)如果所需特性是变量,将它当作参数传递给目标函数。本例中是将_daysOverdrawn变量作为参数传递给目标函数。
        调整目标函数后,编译,然后就可以将原函数的函数本体替换为一个简单的委托动作,然后编译并测试。
class Account {
	double overdraftCharge() {
		return _type.overdraftCharge(_daysOverdrawn);
}
        可以保留代码已有的样式,也可以删除原函数。如果想删除原函数,就需要找出所有的调用者,并将调用者重新定向,改为调用Account的bankCharge()。
class Account {
	double bankCharge() {
		double result = 4.5;
		if (_daysOverdrawn > 0)
			result += _type.overdraftCharge(_daysOverdrawn);
		return result;
	}
}
        所有调用点都修改完成后,再删除原函数在Account中的声明。可以在每次删除之后编译并测试,也可以一次性批量完成,但是需要特别细心。如果被搬移的函数不是private的,还需要检查其它类是否使用了这个函数。
        由于本例中被搬移的函数只引用了一个字段,所以只需将该字段以参数传递就行。如果被搬移函数调用了Account中的另一个函数,就不能这个简单处理了。这种情况必须将原对象传递给目标函数。
class AccountType {
	double overdraftCharge(Account account) {
		if (isPremium()) {
			double result = 10;
			if (account.getDaysOverdrawn() > 7)
				result += (account.getDaysOverdrawn() - 7) * 0.85;
			return result;
		} else{
			return account.getDaysOverdrawn() * 1.75;
		}
	}
}
        某些情况下需要原类的多个特性,那么也会将原对象传递给目标函数。但是,如果目标函数需要太多原类特性,就需要进一步重构。通常会分解目标函数,并将其中一部分移回原类。


        本文主要介绍了重构手法——搬移函数。我们会经常地使用该重构手法,特别是当我们发现某个带有参数函数不仅仅适用于当前类,还适用于其它的类,这样就可以将该函数抽取为一个独立的函数,放入工具类中,以备其它类在具有同样的情形下可以使用。
        最后,希望本文对你有所帮助。有问题可以留言,谢谢。(PS:下一篇将介绍重构笔记——搬移字段)


重构笔记文章如下

       重构笔记——入门篇

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

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

       重构笔记——构筑测试体

       重构笔记——提炼函数

       重构笔记——内联函数

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

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

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

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

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



重构笔记——搬移函数