Litong's Blog

Work to become, not to acquire.

第12章 处理继承关系

与任何强有力的特性一样,继承机制十分实用,却也经常被误用,而且常得你用上一段时间,遇见了痛点,才能察觉误用所在。

特性经常需要在继承体系里上下调整。可以使用函数上移、函数下移以及字段下移、提炼超类、移除子类以及折叠继承体系等手段。

如果一个字段仅仅作为类型码使用,根据其值来触发不同的行为,那么我会通过以子类取代类型码。

当继承的场景不再合适,可以用委托子类或以委托取代超类等手段。

函数上移(Pull Up Method)

避免重复代码是很重要的。重复经常会滋生bug,并且找出重复也有一定的难度。

字段上移(Pull Up Field)

字段上移消除了重复的数据声明,甚至可以将该字段的行为从子类移至超类,从而去除重复的行为。

函数下移(Push Down Method)

如果超类中的某个函数只与一个或少数几个子类有关,那么最好将其从超类中挪走,放到真正关心它的子类中去。

字段下移(Push Down Field)

如果某个字段只被一个子类或少数子类用到,就将其搬移到需要该字段的子类中。

以子类取代类型码(Replace Type Code with Subclasses)

软件系统经常需要表现“相似但又不同的东西”。表现分类关系的第一种工具是类型码字段。

类型码的取值经常来自给系统提供数据的外部服务。

继承有两个诱人之处,首先你可以用多态来处理条件逻辑,另外,有些字段或函数只对特定的类型码取值才有意义。

子类的形式能更明确表达数据与类型之间的关系。

移除子类(Remove Subclass)

随着软件的演化,子类所支持的变化可能会被搬移到别处,甚至完全去除,这时子类就失去了价值。

子类存在着就有成本,阅读者要花心思去理解它的用意。

提炼超类(Extract Superclass)

很多时候,合理的继承关系是在程序演化的过程中才浮现出来的:发现了一些共同元素,然后希望抽取出来,于是才有了继承关系。

提炼超类是相对简单的做法,如果不行还可以考虑以委托取代超类。

折叠继承体系(Collapse Hierarchy)

当一个类与其超类差别不大时,不再值得作为独立的类存在,此时可以将子类和超类合并起来。

以委托取代子类(Replace Subclass with Delegate)

如果一个对象的行为有明显的类别之分,继承是很自然的表达方式。

但继承也有短板。导致不同行为的原因可能有多种,但继承只能用于处理一个方向上的变化。

更大的问题在于,继承给类之间引入了非常紧密的关系。在超类上做任何修改,都有可能破坏子类。

上面的问题都可以用委托来解决。对于不同方向上的变化原因,可以委托给不同的类。使用委托关系时接口更清晰、耦合更少。

对象组合优于继承,但其适用于在继承开始出现问题的时候,大部分时候还是可以先从继承开始。

以委托取代子类并非总会需要建立一个继承体系来接收委托,不过建立一个状态或策略的继承体系经常都是有用的。

以委托取代超类(Replace Superclass with Delegate)

继承也有可能造成困扰和混乱。

如果超类的一些函数对子类并不适用,就说明我不应该通过继承来获得超类的功能。

继承还可以会带来“类型与实例名不符实”的不易察觉的建模错误。

上述问题可以使用委托关系避免,委托关系能更清晰地表达“这是另一个东西,我只是需要用到其中携带的一些功能”这层意思。

如果子类与超类之间的耦合过强,超类的变化很容易破坏子类的功能,此时还是建议以委托取代超类。

先使用继承,如果发现继承有问题,再使用以委托取代继承。