《重构 改善既有代码的设计》 读书笔记(二十二)

6.9 替换算法(Substitute Algorithm)

你想要把某个算法替换为另一个更清晰的算法。

将函数本体替换为另一个算法。

String foundPersone(String[] people){
    for (int i = 0; i < people.length; i++){
        if(people[i].equals("Don"))
            return "Don";
        if(people[i].equals("John"))
            return "John";
        if(people[i].equals("Kent"))
            return "Kent";
    }
    return "";
}

>>>
String foundPersone(String[] people){
    List candidates = Arrays.asList(
        	new String[]{"Don","John","Kent"});
    for (int i = 0; i < people.length; i++){
        if(candidates.contains(people[i]))
            return people[i];
    }
    return "";
}

动机

解决问题的办法有很多,我们采取的那种往往可能不是最简单的。所以,当我们发现做一件事有更清晰、更简单、更高效的方式,就应该去修改。

“重构”可以把复杂东西变得简单,但随着时间,对问题有了更深的理解,有可能你发现这种重构不太合适,此时就应该改变原有的重构结构——改成一种更简单的方式。

使用这项重构手法之前,务必确认自己已经尽可能分解了原先函数:替换一个庞大的算法很难,分解成小型函数,能够更好的进行算法替换工作。

做法

  1. 准备好另一个(替换用的)算法,确认它的确能正常编译。

  2. 针对现有测试,执行上述的新算法。如果结果与原本结果相同,重构结束。

  3. 如果测试结果不同于原先,在测试和调试过程中,以旧算法为参照去比较。

第七章 在对象之间搬移特性

单一职责:就一个类而言,应该仅有一个引起它变化的原因。
我们之前提到过一种坏味道,名字叫做发散式变化3.5节),那里假设了一种情况:当A需求改变时,本类要修改abc三个方法,当B需求改变时,本类修改的却是def三个方法,这种情况下就形成了责任分散——事实上abc应该在一个类中,def在另一个类中更为合理。

但是,在设计过程中,没有谁能够保证一次性把类设计好,让其符合单一职责。还好,我们有重构,可以根据需求改变原先的设计。

通常情况,使用搬移函数(7.1 Move Method)和搬移字段(7.2 Move Field)就能解决大多数问题。

而二者的先后顺序,往往是先搬移字段,后搬移函数。

当一个类呈现出发散式变化(3.5节)这样的坏味道时,就该考虑按照那一节所讲的,用提炼类(7.3 Extract Class)的方式将一部分责任分离出去。

当多个类具有同一种责任(霰弹式修改3.6节):修改一个需求,要同时动很多类的内容)时,就该用将类内联化(7.4 Inline Class)的方法把职责集中起来。

如果你不希望看到某两个类之间存在依赖,那么就可以运用隐藏委托关系(7.5 Hide Delegate)的方式为二者加一个中间人。

有时,过度的隐藏委托类会导致拥有者的接口经常变化,此时就该移除中间人(7.6 Remove Middle Man)。

本章最后两个重构我们之前介绍过:在不完美类库3.19 Incomplete Library Class)中提到的那俩。

引入外加函数(7.7 Introduce Foreign Method)和引入本地扩展(7.8 Introduce Local Extension),这两个可以在类库的基础上增加方法,变得更符合我们的需求。

7.1 搬移函数(Move Method)

你的程序中,有个函数与其所驻类之外的另一个类进行更多交流:调用后者,或被后者调用。

在该函数最常引用的类中建立一个有着类似行为的新函数,然后把旧函数变成单纯的委托函数,或是直接用新函数替换旧函数的引用点。

class A{
	void b(){
		System.out.println("I'm B.");
	}
}
class B{}

>>>
class A{}
class B{
	void b(){
		System.out.println("I'm B.");
	}
}

动机

搬移函数是重构理论的支柱,它贯穿了整本书,贯穿了大多数重构方法的内部。

搬移的使用时机是:一个类有太多行为,或者一个类中的某个方法与另一个类太过亲密。

通过搬移函数,能让系统中的类更简单,这些类最终也将更干净利落地实现系统交付的任务。

如果一个方法中,使用另一个对象的次数比使用自己所驻对象的次数还多,就应该观察一下调用它的那个位置、以及它调用的方法,以及继承体系中它的覆写方法。然后,根据这个函数与哪个对象最密切,决定移动路径。

如果说不确定是否应该搬移函数,此时请暂时放下,因为不恰当地搬移会适得其反。

做法

  1. 检查源类中被源函数所使用的一切特性(包括字段和函数),考虑它们是否也该被搬移。

如果某个特性只在此方法中使用,那么就应该将它一并随着此方法搬移。如果此特性不止在此方法中调用,看看那些调用此特性的方法能不能也拐卖走。

  1. 检查源类的子类和超类,看看是否有该函数的其他声明。

  2. 在目标类中声明这个函数(别忘了给它起个好名字)。

  3. 将源函数的代码复制到搬移后的新函数中。调整新函数,让其能够运行。

如果新函数使用了源类中的特性,就得决定如何从新函数那里引用源对象。如果新函数所在类不适合引用源对象,那就可以给新函数加一个参数传递源对象。
如果源函数包含异常处理,就得判断逻辑上应该由哪个类来处理这一异常。如果应该由源类来负责,就该把异常处理留在原地。

  1. 编译新函数所在类,确保没有报错。

  2. 决定如何从源函数中引用目标对象。

如果有现成字段或方法能取得目标对象,那是极好的,如果没有,就该看看能否轻松建立一个这样的函数。如果还不行,就在源类中新建一个专门字段去保存目标对象。

  1. 修改源函数,让其成为一个纯委托函数。

纯委托函数:函数内部只有一句代码,那句代码就是调用方法的语句。

  1. 编译、测试。

  2. 决定是否删除旧函数。

如果经常在源对象中引用目标函数,那么将源对象作为委托函数更好一点,这样就保证了不会重复的写很多次引用目标函数的语句。

  1. 如果要移除源函数,记得查找所有调用点,然后替换掉。

  2. 再编译、再测试。

范例

用一个表示“账户”的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;
	}
	
	double bankCharge() {
		double result = 4.5;
		if(daysOverdrawn>0)
			result += overdraftCharge();
		return result;
	}
}

假设有几种新账户,每一种都有自己的“透支金额计费规则”。所以我希望将overdraftCharge()搬移到AccountType类去。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NewReErWen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值