天天看点

《重构:改善既有代码的设计》—第1章1.4节运用多态取代与价格相关的条件逻辑

本节书摘来自异步社区《重构:改善既有代码的设计》一书中的第1章,第1.4节运用多态取代与价格相关的条件逻辑,作者【美】martin fowler,更多章节内容可以访问云栖社区“异步社区”公众号查看。

1.4 运用多态取代与价格相关的条件逻辑

这个问题的第一部分是switch语句。最好不要在另一个对象的属性基础上运用switch语句。如果不得不使用,也应该在对象自己的数据上使用,而不是在别人的数据上使用。

这暗示getcharge()应该移到movie类里去:

为了让它得以运作,我必须把租期长度作为参数传递进去。当然,租期长度来自rental对象。计算费用时需要两项数据:租期长度和影片类型。为什么我选择将租期长度传给movie对象,而不是将影片类型传给rental对象呢?因为本系统可能发生的变化是加入新影片类型,这种变化带有不稳定倾向。如果影片类型有所变化,我希望尽量控制它造成的影响,所以选择在movie对象内计算费用。

我把上述计费方法放进movie类,然后修改rental的getcharge(),让它使用这个新函数(图1-12和图1-13):

《重构:改善既有代码的设计》—第1章1.4节运用多态取代与价格相关的条件逻辑
《重构:改善既有代码的设计》—第1章1.4节运用多态取代与价格相关的条件逻辑

搬移getcharge()之后,我以相同手法处理常客积分计算。这样我就把根据影片类型而变化的所有东西,都放到了影片类型所属的类中。以下是重构前的代码:

重构后的代码如下:

终于……我们来到继承

我们有数种影片类型,它们以不同的方式回答相同的问题。这听起来很像子类的工作。我们可以建立movie的三个子类,每个都有自己的计费法(图1-14)。

《重构:改善既有代码的设计》—第1章1.4节运用多态取代与价格相关的条件逻辑

这么一来,我就可以用多态来取代switch语句了。很遗憾的是这里有个小问题,不能这么干。一部影片可以在生命周期内修改自己的分类,一个对象却不能在生命周期内修改自己所属的类。不过还是有一个解决方法:state模式[gang of four]。运用它之后,我们的类看起来像图1-15。

《重构:改善既有代码的设计》—第1章1.4节运用多态取代与价格相关的条件逻辑

加入这一层间接性,我们就可以在price对象内进行子类化动作[4],于是便可在任何必要时刻修改价格。

如果你很熟悉gof(gang of four,四巨头)[5]所列的各种模式,可能会问:“这是一个state,还是一个strategy?”答案取决于price类究竟代表计费方式(此时我喜欢把它叫做pricer还pricingstrategy),还是代表影片的某个状态(例如“star trek x是一部新片”)。在这个阶段,对于模式(和其名称)的选择反映出你对结构的想法。此刻我把它视为影片的某种状态。如果未来我觉得strategy能更好地说明我的意图,我会再重构它,修改名字,以形成strategy。

为了引入state模式,我使用三个重构手法。首先运用replace type code with state/strategy (227),将与类型相关的行为搬移至state模式内。然后运用move method (142)将switch语句移到price类。最后运用replace conditional with polymorphism (255)去掉switch语句。

首先我要使用replace type code with state/strategy (227)。第一步骤是针对类型代码使用self encapsulate field (171),确保任何时候都通过取值函数和设值函数来访问类型代码。多数访问操作来自其他类,它们已经在使用取值函数。但构造函数仍然直接访问价格代码[6]:

我可以用一个设值函数来代替:

然后编译并测试,确保没有破坏任何东西。现在我新建一个price类,并在其中提供类型相关的行为。为了实现这一点,我在price类内加入一个抽象函数,并在所有子类中加上对应的具体函数:

然后就可以编译这些新建的类了。

现在,我需要修改movie类内的“价格代号”访问函数(取值函数/设值函数,如下),让它们使用新类。下面是重构前的样子:

这意味着我必须在movie类内保存一个price对象,而不再是保存一个_pricecode变量。此外我还需要修改访问函数:

现在我可以重新编译并测试,那些比较复杂的函数根本不知道世界已经变了个样儿。

现在我要对getcharge()实施move method (142)。下面是重构前的代码:

搬移动作很简单。下面是重构后的代码:

搬移之后,我就可以开始运用replace conditional with polymorphism (255)了。

下面是重构前的代码:

我的做法是一次取出一个case分支,在相应的类建立一个覆盖函数。先从regularprice开始:

这个函数覆盖了父类中的case语句,而我暂时还把后者留在原处不动。现在编译并测试,然后取出下一个case分支,再编译并测试。(为了保证被执行的确实是子类中的代码,我喜欢故意丢一个错误进去,然后让它运行,让测试失败。噢,我是不是有点太偏执了?)

处理完所有case分支之后,我就把price.getcharge()声明为abstract:

现在我可以运用同样手法处理getfrequentrenterpoints()。重构前的样子如下[7]:

首先我把这个函数移到price类:

但是这一次我不把超类函数声明为abstract。我只是为新片类型增加一个覆写函数,并在超类内留下一个已定义的函数,使它成为一种默认行为。

引入state模式花了我不少力气,值得吗?这么做的收获是:如果我要修改任何与价格有关的行为,或是添加新的定价标准,或是加入其他取决于价格的行为,程序的修改会容易得多。这个程序的其余部分并不知道我运用了state模式。对于我目前拥有的这么几个小量行为来说,任何功能或特性上的修改也许都不合算,但如果在一个更复杂的系统中,有十多个与价格相关的函数,程序的修改难易度就会有很大的区别。以上所有修改都是小步骤进行,进度似乎太过缓慢,但是我一次都没有打开过调试器,所以整个过程实际上很快就过去了。我写本章文字所用的时间,远比修改那些代码的时间多得多。

现在我已经完成了第二个重要的重构行为。从此,修改影片分类结构,或是改变费用计算规则、改变常客积分计算规则,都容易多了。图1-16和图1-17描述state模式对于价格信息所起的作用。

《重构:改善既有代码的设计》—第1章1.4节运用多态取代与价格相关的条件逻辑
《重构:改善既有代码的设计》—第1章1.4节运用多态取代与价格相关的条件逻辑