文章目录
在上一篇文章 《重构:改善既有代码的设计(第2版)》示例(C++版)(一)中,我们对一个有关于剧目演出的代码,并且花费了大量的动作将其重构,在重构的过程中保持重构-测试-提交这种小步快跑的模式,最终将原来大泥团式的代码拆分成结构比较清晰,易于阅读的代码。
但是在上篇文章末尾,留下下来一个新的需求问题需要解决,即剧团需要支持更多类型的剧种,以及支持它们各自的价格计算和观众量积分计算。
新的需求
时代在发展,剧团也要改革进步,未来会引进更多的剧种,他们的结算方式不尽相同。为了应对未来将要到来的变化,我们需要做一些改变。但是由于未来引入什么剧种,那么其结算方式也都是未定的。但有一点可以确定的是,一旦有新的剧种出现,我们就得添加新的结算方式。
按照当前的结构,我只需要在计算函数里面添加分支逻辑即可。AmountForPerf函数清楚体现了,戏剧类型在计算分支的选择上起着关键的作用。但这样的分支逻辑很容易随着代码堆积而腐化。其实在看到switch出现时候就应该保持有防止代码腐化的警惕之心。
最好不要在另一个对象的属性基础上运用switch语句。如果不得不使用,也应该在对象自己的数据上使用,而不是在别人的数据上使用。我们尝试抽象出一个抽象类IPlay,它有成员函数GetAmount。为了消灭switch,我们需要为每一个原来的枚举类型实现一个子类,在每个子类中实现自己的计算方法。下面是具体的UML图。
生成继承体系用于计算
千万不要心急,不如我们再往前多走一点点。原有的设计存在一个小问题:一个剧目是可以在其生命周期内修改自己的计算费用的算法的。当剧团期望剧目根据淡季旺季修改其结算方式时候,我们并不能在每个剧种生命周期内修改自己结算方式。
为解决这个问题,这时候我们就需要设计模式了——State(状态)模式。至于说具体应该是State(状态)模式还是Strategy(策略)模式,那就是另一个话题了。我们来看UML图:
要为程序引入结构、显示地表达出“计算逻辑的差异是由类型代码确定”有许多途径,不过最自然的解决办法还是使用面向对象世界里的一个经典特性——类型多态。以多态取代条件表达式也是常见的重构手法之一。在具体实施之前,我的设想是建立一个继承体系,它有两个子类,分别对应着喜剧和悲剧,再将其独有的计算体系搬移进子类的具体实现中去。这里增加了间接层——PerformanceCalculator充当计算费用的计算器。我们可以对他进行子类化动作,就可以修改计算算法了。
class PerformanceCalculator {
public:
virtual int GetAmount(int audience) = 0;
virtual int GetVolumeCredits(int audience) {
int result = 0;
result += max(audience - 30, 0);
return result;
}
};
class TragedyCalculator : public PerformanceCalculator {
public:
int GetAmount(int audience) override {
int result = 40000;
if (audience > 30) {
result += 1000 * (audience - 30);
}
return result;
}
};
class CemodyCalculator : public PerformanceCalculator {
public:
int GetAmount(int audience) override