🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### Tease Apart Inheritance(梳理并分解继承体系) 某个继承体系(inheritance hierarchy )同时承担两项责任。 建立两个继承体系,并通过委托关系(delegation)让其中一个可以调用另一个。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e7ea705.gif) **动机(Motivation)** 继承是个好东西,它使你得以在subclass 中写出明显「压缩过」(compressed)的 代码。函数的重要性可能并不和它的大小成比例——在继承体系之中尤然。 不过,先别急着为这个强大的工具欢呼雀跃,因为继承也很容易被误用,并且这种误用还很容易在开发人员之间蔓延。今天你为了一项小小任务而加入一个小小的subclass ;明天又为同样任务在继承体系的另一个地方加入另一个subclass 。一个星期(或者一个月或者一年)之后,你就会发现自己身陷泥淖,而且连一根拐杖都没有。 混乱的继承体系是一个严重的问题,因为它会导致重复代码,而后者正是程序员生涯的致命毒药。它还会使修改变得困难,因为「特定种类」的问题的解决策略被分散到了整个继承体系。最终,你的代码将非常难以理解。你无法简单地说:『这就 是我的继承体系,它能计算结果』,而必须说:『它会计算出结果……呃,这些是 用以表现不同表格形式的subclasses ,每个subclass 又有一些subclasses 针对不同的 国家。』 要指出「某个继承体系承担了两项不同的责任」并不困难:如果继承体系中的某一特定层级上的所有classes,其subclass 名称都以相同的形容词开始,那么这个体系很可能就是承担着两项不同的责任。 **作法(Mechanics)** - 首先识别出继承体系所承担的不同责任,然后建立一个二维表格(或者三维乃至四维表格,如果你的继承体系够混乱而你的绘图工具够酷的话),并以坐标轴标示出不同的任务。我们将重复运用本重构,处理两个或两个以上的维度〔当然,每次只处理一个维度〉。 - 判断哪一项责任更重要些,并准备将它留在当前的继承体系中。准备将另一项责任移到另一个继承体系中。 - 使用Extract Class 从当前的superclass 提炼出一个新class ,用以表示重要性稍低的责任,并在原superclass 中添加一个instance 变量(不是static 变量〕,用以保存新建class 的实体。 - 对应于原继承体系中的每个subclass ,创建上述新class 的一个个subclasses 。在原继承体系的subclasses 中,将前一步骤所添加的instance 变量初始化为新建subclass 的实体。 - 针对原继承体系中每个subclass ,使用Move Method 将其中的行为搬移到与之对应的新建subclass 中。 - 当原继承体系中的某个subclass 不再有任何代码时,就将它去除。 - 重复以上步骤,直到原继承体系中的所有subclass 都被处理过为止。观察新继承体系,看看是否有可能对它实施其他重构手法,例如Pull Up Method 或 Pull Up Field 。 **范例:(Example)** 让我们来看一个混乱的继承体系(如图12.1)。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e80dfab.gif) 图12.1 一个混乱的继承体系 这个继承体系之所以混乱,因为一开始Deal class 只被用来显示单笔交易。后来,某个人突发奇想地用它来显示一张交易表格。只需飞快建立一个ActiveDeal subclass 再加上一点点经验,不必做太多工作就可以显示一张表格了。哦,还要「被动交易(PassiveDeal)」表格是吗?没问题,再加一个subclass 就行了。 两个月过去,表格相关代码变得愈来愈复杂,你却没有一个好地方可以放它们,因为时间太紧了。咳,老戏码!现在你将很难向系统加入新种交易,因为「交易处理逻辑」与「数据显示逻辑」已经「你中有我,我中有你」了。 按照本重构提出的处方笺,第一步工作是识别出这个继承体系所承担的各项责任。 这个继承体系的职责之一是捕捉不同交易种类间的变化(差异〕,职责之二是捕捉 不同显示风格之间的变化(差异〕。因此,我们可以得到下列表格: ~~~ Deal Active Deal Passive DealTabular Deal ~~~ 下一步要判断哪一项职责更重要。很明显「交易种类」比「显示风格」重要,因此我们把「交易种类」留在原地,把「显示风格」提炼到另一个继承体系中。不过,实际工作中,我们可能需要将「代码较多」的职责留在原地,这样一来需要搬移的 代码数量会比较少。 然后,我们应该使用Extract Class 提炼出一个单独的PresentationStyle class 用以表示「显示风格」(如图12.2)。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e822381.gif) 图12.2 添加PresentationStyle ,用以表示「显示风格」 接下来我们需要针对原继承体系中的每个subclass ,建立PresentationStyle 的一个个subclasses (如图 12.3),并将Deal class之中用来保存PresentationStyle 实体的那个instance 变量初始化为适当的subclass 实体: ![](https://box.kancloud.cn/2016-08-15_57b1b5e836737.gif) 图12.3 为PresentationStyle 添加subclasses ~~~ ActiveDeal constructor ...presentation= new SingleActivePresentationStyle();... ~~~ 你可能会说:『这不是比原先的classes 数量还多了吗?难道这还能让我的生活更舒服?』生活往往如此:以退为进,走得更远。对一个纠结成团的继承体系来说,被提炼出来的另一个继承体系几乎总是可以再戏剧性地大量简化。不过,比较安全的态度是一次一小步,不要过于躁进。 现在,我们要使用 Move Method 和 Move Field,将Deal subclass 中[与显示逻辑相关」的函数和变量搬移到PresentationStyle 相应的subclasses 去。我们想不出什么好办法来模拟这个过程,只好请你自己想像。总之,这个步骤完成后,TabularActiveDeal 和 TabularPassiveDeal不再有任何代码,因此我们将它们移除(如图12.4)。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e84c140.gif) 图12.4 与表格相关的 Deal subclass 都移除了 两项职责被分割之后,我们可以分别简化两个继承体系。一旦本重构完成,我们总是能够大大简化被提炼出来的新继承体系,而且通常还可以简化原继承体系。 下一步,我们将摆脱「显示风格」中的主动(active)与被动(passive)区别,如图 12.5。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e85f190.gif) 图12.5 继承体系被分割了 就连「单一显示」和「表格显示」之间的区别,都可以运用若干变量值来捕捉,根本不需要为它们建立subclasses (如图12.6〕。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e874ff4.gif) 图12.6 「显示风格」(presentation style)之间的差异可以使用一些变量来表现