六、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、编译、测试。