第十一章 处理概括关系
有一批重构手法专门用于处理类的概括关系(generalization,即继承关系),其中主要是将函数上下移动于继承体系之中。
1、上移字段(Pull Up Field)
判断若干字段是否重复,唯一的办法就是观察函数如何使用它们。如果它们被函数使用的方式很相似,你就可以将它们归纳到超类去。本项重构从两方面减少重复:首先它去除了重复的数据声明;其次它使你可以将使用该字段的行为从子类移至超类,从而去除重复的行为。
2、上移函数(Pull Up Method)
避免重复是很重要的!重复只会成为错误的滋生地。无论何时,只要系统之内出现重复,你就会面临“修改其中一处却未能修改另一个”的风险。上移函数(Pull Up Method)是一项很自然的重构。
3、构造函数本体上移(Pull Up Constructor Body)
本项重构手法用于去掉构造函数中的重复代码。
4、下移函数(Pull Down Method)
如果超类(基类)中的某个函数只与部分子类有关,那么我们应该将该函数从超类(基类)中下移至特定的子类中(即需要这个特性的子类)。当然,如果你想通过基类对象访问该函数,为了顺利进行,你需要在超类(基类)中将该函数声明为虚函数(virtual),并提供一个默认的实现(或空实现,但不能为纯虚函数)。
5、下移字段(Pull Down Field)
如果超类(基类)中的某个字段只被部分子类用到,那么就应该将这个字段移至哪些需要这个字段的子类中去。
6、提炼子类(Extract Subclass)
如果类中的某些特性只被某些实例用到,这是需要“提炼子类”信号。新建一个类,将上面所说的那一部分特性移到子类中。
Extract Class 和 Extract Subclass 其实就是“委托”和“继承”的抉择。如果一个类承担了超过一个职责,则应该使用提炼类(Extract Class),将其中的部分责任委托给新提炼的类去完成。如果一个类的有些实例用不到它的所有特性,则应该提炼出一个子类(Extract Subclass),并将这部分特性搬移到新提炼的子类中去。
7、提炼超类(Extract Superclass)
提炼超(基)类。如果两个类有相似特性,那么应该将这些相似的特性提炼到一个基类中。 重复代码是系统中最糟糕的东西!如果你在不同地方做同一件事情,一旦需要修改那些动作,你就得平白做更多的修改。
8、提炼接口(Extract Interface)
C++多重继承的替代方案。
9、折叠(合并)继承体系(Collapse Hierarchy)
重构不仅会建立继承体系,也可能会折叠(去除)继承体系。如果我们发现在一个继承体系中,某个子类并未带来该有的价值,我们应该果断移除它。
10、塑造模板函数(Form Template Method)
塑造模板函数其实就是“定义好处理某种事情的具体步骤(或算法的骨架)”,尽管这些具体步骤的细节可能不同,但这些不同可以通过多态来实现。
11、以委托取代继承(Replace Inheritance With Delegation)
我们经常纠结于是使用“继承”还是使用“委托”?通常,如果某个子类只使用超类接口中的一部分,或者根本不需要继承而来的数据,这种情况使用委托会更合适。
很多时候,你一开始继承了一个类,随后你发现基类中的许多操作并不是真正适用于子类。这种情况下,你所拥有的接口并未真正反映出子类的功能。
你可以选择容忍,因为这种情况通常并不会影响你实现功能。但是,这样的代码所传达的信息与你的意图南辕北撤-----这是一种混淆,我们应该将其去除。
如果以委托取代继承,你可以更清楚的表明你的意图:你只需要受托类的一部分功能。接口中的哪一部分应该被使用,哪一部分应该被忽略,完全由你主导控制。当然,这样做也是有成本的,即你需要额外的编写出委托函数,但这通常不是问题,因为编写这样的委托函数极其简单。
12、以继承取代委托(Replace Delegation With Inheritance)
面向对象程序设计中有一条很经典的原则是:优先使用委托而不是继承。这是因为继承往往意味着紧耦合,而委托通常能带来松耦合的系统。但是,有时你也会因为过度信任委托而出错。例如,如果你发现自已需要使用委托类的所有函数,并且费了很大力气编写所有极简的委托函数,那么你应该实施本项重构。
当然,我们说过:优先使用委托而不是继承。所以,在使用本项重构之前,有两条告诫需要牢记于心:
首先,如果你并没有使用受托类的所有函数,那么就不要实施本项重构,因为子类应该总是遵循基类的接口(里氏替换原则)。就算过多的委托函数让你烦心,你仍然有别的选择,还记得“移除中间人(Remove Middle Man)”那项重构吗?你可以直接返回受托对象给客户端,让客户代码自己去调用需要的函数,从而可以免除你编写众多委托函数的麻烦。
其次,如果受托对象被不止一个其他对象共享,而且受托对象是可变的。那么此时你也应该停止本项重构。数据共享是必须由委托关系承担的一种责任,你无法把它转给继承关系。当然,如果仅仅只是数据共享,而数据对象是不可变的,那么我们可以复制对象达到数据共享的目的。