《重构改善既有代码的设计》之重构列表--简化函数调用(二)

六、Replace Parameter with explicit methods(以明确函数取代参数)

你有一个函数,其中完全取决于参数值而采取不同行为。

针对该参数的每一个可能值,建立一个独立函数。

动机

Replace parameter with explicit methods恰恰相反于parameterize method。如果某个参数有多种可能的值,而函数内又以条件表达式检查这些参数值,并根据不同参数值做出不同的行为,那么就应该使用本项重构。调用者原本必须赋予参数适当的值,以绝对该函数做出何种响应。现在,既然你提供了不同的函数给调用者使用,就可以避免出现条件表达式。此外你还可以获得编译器检查的好处,而且接口也更清楚。如果以参数值决定函数行为,那么函数用户不但需要观察该函数,而且还要判断参数值是否合法,而“合法的参数值”往往很少在文档中被清楚的提出。

就算不考虑编译器检查的好处,只是为了获得一个清晰的接口,也值得你执行本项重构。哪怕只是给一个内部的布尔变量赋值,相较之下,Seitch.beOn()也比Switch.setState(true)要清楚的多。

但是,如果参数值不会对函数行为有太多影响,你就不应该使用Replace parameter with explicit methods。如果情况真是这样,而你也只需要通过参数为一个字段赋值,那么直接使用设置函数就行了。如果的确需要条件判断的行为,可考虑使用Replace Conditional with Polymorphism

做法

1、针对参数的每一种可能值,新建一个明确函数。

2、修改条件表达式的每个分支,使其调用合适的新函数。

3、修改每个分支后,编译并测试。

4、修改原函数的每一个被调用点,改而调用上述的某个合适的新函数。

5、编译,测试。

6、所有调用端都修改完毕后,删除原函数。

七、Preserve whole Object(保持对象完整)

你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。

改为传递整个对象。

动机

有时候,你会将来自同一对象的若干项数据作为参数,传递给某个函数。这样做的问题在于:万一将来被调用函数需要新的数据项,你就必须查找并修改对此函数的所有调用。如果你把这些数据所属的整个对象传给函数,可以避免这种尴尬的处境,因为被调用函数可以向那个参数对象请求任何它想要的信息。

除了可以使参数列更稳固以外,Preserve whole Object往往还能提高代码的可读性。过长的参数列很难使用,因为调用者和被调用者都必须记住这些参数的用途。此外,不使用完整对象也会造成重复代码,因为被调用函数无法利用完整对象中的函数来计算某些中间值。

不过事情总有两面:如果你传的是数值,被调用函数就只依赖于这些数值,而不依赖它们所属的对象。但你传递的是整个对象,被调用函数所在的对象就需要依赖参数对象。如果这会使你的依赖结构恶化,那么就不该使用Preserve whole Object

我还听过另一种不使用Preserve whole Object的理由:如果被调用函数只需要参数对象的一项数据,那么只传递那个数值会更好。我并不认同这种观点,因为传递一项数值和传递一个对象,至少在代码清晰度上市等价的(当然对于按值传递的参数来说,性能上可能有所差异)。更重要的考量应该放在对象之间的依赖关系上。

如果被调用函数使用了来自另一个对象的很多数据,这可能意味该函数实际上应该被定义在那些数据所属对象中。所以,考虑Preserve whole Object的同时,你也应该考虑Move Method

运用本项重构之前,你可能还没有定义一个完整对象。那么就应该先使用Introduce Parameter Object

还有一种常见情况:调用者将自己的若干数据作为参数,传递给被调用函数。这种情况下,如果该对象有合适的取值函数,你可以使用this取代这些参数值,并且无需操心对象依赖问题。

做法

1、对你的目标函数新添一个参数项,用以代表原数据所在的完整对象。

2、编译、测试。

3、判断哪些函数可被包含在新添的完整对象中。

4、选择上述参数之一,将被调用函数中原来的引用该参数的地方,改为调用新添参数对象的相应取值函数。

5、删除该项参数

6、编译、测试

7、针对所有可从完整对象中获得的参数,重复上述过程。

8、删除调用端那些带有被删除参数的代码。

9、编译、测试。

八、Replace Parameter with Methods(以函数取代参数)

对象调用某个函数,并将所得结果作为参数,传递给另一个函数。而接受该参数的函数本书也能够调用前一个函数。

让参数接受者去除该项参数,并直接调用前一个函数。

动机

如果函数可以通过其他途径获得参数值,那么他就不应该通过参数取得该值。过长的参数列会增加程序阅读者的理解难度,因为我们应该尽可能缩短参数列的长度。

做法

1、如果有必要,将参数的计算过程提炼到一个独立函数中。

2、将函数本体内引用该参数的地方改为调用新建的函数。

3、每次替换后,编译并测试。

4、全部替换完成后,使用Remove Parameter将该参数去掉。

九、Introduce Parameter Object(引入参数对象)

某些参数总是很自然地同时出现。

以一个对象取代这些参数。

动机

你常会看到特定的一组参数总是一起被传递。可能有好几个函数都使用这一组参数,这些参数可能隶属同一个类,也可能隶属不同的类。这样一组参数就是所谓的Data Clumps(数据泥团),我们可以运用一个对象包装所有这些数据,再以该对象取代它们。哪怕只是为了把这些数据组织在一起,这样做也是值得的。本项重构的价值在于缩短参数列,而你知道,过长的参数列总是难以理解的。此外,新对象所定义的访问函数还可以使代码更具一致性,这又进一步降低了理解和修改代码的难度。

本项重构还可以带给你更多好处。当你把这些参数组织到一起后,往往很快可以发现一些课被移至新建类的行为。通常,原本使用那些参数的函数对这一组参数会有一些共同的处理,如果将这些共同行为移至新对象中,你可以减少很多重复代码。

做法

1、新建一个类,用以表现你想替换的一组参数。将这个类设为不可变的。

2、编译。

3、针对使用该组参数的所有函数,实施Add Parameter,传入上述新建类的实例对象,并将此参数值设为null

如果你所修改的函数被其他很多函数调用,那么可以保留修改前的旧函数,并令它调用修改后的新函数。你可以先对旧函数进行重构,然后逐一修改调用端使其调用新函数,最后再将旧函数删除。

4、对于Data Clumps中的每一项(在此均为参数),从函数签名中移除之,并修改调用端和函数本体,令它们都改而通过新的参数对象取得该值。

5、每去除一个参数,编译并测试。

6、将原先的参数全部去除之后,观察有无适当函数可以运用Move Method搬移到参数对象之中。

被搬移的可能是整个函数,也可能是函数中的一个段落。如果是后者,首先使用Extract Method将该段落提炼为一个独立函数,再搬移这一新建函数。

十、Remove Setting Method(移除设值函数)

类中的某个字段应该在对象创建时被设值,然后就不再改变。

动机

如果你为某个字段提供了设值函数,这就暗示了这个字段值可以被改变。如果你不希望在对象创建之后此字段还有机会被改变,那就不要为它提供设值函数(同时将该字段设为final)。这样你的意图会更加清晰,并且可以排除其值被修改的可能性--这种可能性往往是非常大的。

如果你保留了间接访问变量的方法,就可能经常有程序员盲目的使用它们。这些人甚至会在构造函数中使用设置函数!我猜想他们或许是为了代码的一致性,但却忽视了设值函数往后可能带来的混淆。

做法

1、检查设值函数被使用的情况,看它是否只被构造函数调用,或者被构造函数所调用的另一个函数调用。

2、修改构造函数,使其直接访问设值函数所针对的那个变量。

如果某个子类通过设置函数给超类的某个private字段设了值,那么你就不能这样修改。这种情况下你应该试着在超类中提供一个protected函数(最好是构造函数)来给这些字段设值。不论你怎么做,都不用给超类中的函数起一个与设值函数混淆的名字。

3、编译,测试。

4、移除这个设值函数,将它所针对的字段设为final

5、编译、测试。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值