### 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)之间的差异可以使用一些变量来表现
- 译序 by 侯捷
- 译序 by 熊节
- 序言
- 前言
- 章节一 重构,第一个案例
- 起点
- 重构的第一步
- 分解并重组statement()
- 运用多态(Polymorphism)取代与价格相关的条件逻辑
- 结语
- 章节二 重构原则
- 何谓重构
- 为何重构
- 「重构」助你找到臭虫(bugs)
- 何时重构
- 怎么对经理说?
- 重构的难题
- 重构与设计
- 重构与性能(Performance)
- 重构起源何处?
- 章节三 代码的坏味道
- Duplicated Code(重复的代码)
- Long Method(过长函数)
- Large Class(过大类)
- Long Parameter List(过长参数列)
- Divergent Change(发散式变化)
- Shotgun Surgery(散弹式修改)
- Feature Envy(依恋情结)
- Data Clumps(数据泥团)
- Primitive Obsession(基本型别偏执)
- Switch Statements(switch惊悚现身)
- Parallel Inheritance Hierarchies(平行继承体系)
- Lazy Class(冗赘类)
- Speculative Generality(夸夸其谈未来性)
- Temporary Field(令人迷惑的暂时值域)
- Message Chains(过度耦合的消息链)
- Middle Man(中间转手人)
- Inappropriate Intimacy(狎昵关系)
- Alternative Classes with Different Interfaces(异曲同工的类)
- Incomplete Library Class(不完美的程序库类)
- Data Class(纯稚的数据类)
- Refused Bequest(被拒绝的遗贈)
- Comments(过多的注释)
- 章节四 构筑测试体系
- 自我测试代码的价值
- JUnit测试框架
- 添加更多测试
- 章节五 重构名录
- 重构的记录格式
- 寻找引用点
- 这些重构准则有多成熟
- 章节六 重新组织你的函数
- Extract Method(提炼函数)
- Inline Method(将函数内联化)
- Inline Temp(将临时变量内联化)
- Replace Temp with Query(以查询取代临时变量)
- Introduce Explaining Variable(引入解释性变量)
- Split Temporary Variable(剖解临时变量)
- Remove Assignments to Parameters(移除对参数的赋值动作)
- Replace Method with Method Object(以函数对象取代函数)
- Substitute Algorithm(替换你的算法)
- 章节七 在对象之间搬移特性
- Move Method(搬移函数)
- Move Field(搬移值域)
- Extract Class(提炼类)
- Inline Class(将类内联化)
- Hide Delegate(隐藏「委托关系」)
- Remove Middle Man(移除中间人)
- Introduce Foreign Method(引入外加函数)
- Introduce Local Extension(引入本地扩展)
- 章节八 重新组织数据
- Self Encapsulate Field(自封装值域)
- Replace Data Value with Object(以对象取代数据值)
- Change Value to Reference(将实值对象改为引用对象)
- Replace Array with Object(以对象取代数组)
- Replace Array with Object(以对象取代数组)
- Duplicate Observed Data(复制「被监视数据」)
- Change Unidirectional Association to Bidirectional(将单向关联改为双向)
- Change Bidirectional Association to Unidirectional(将双向关联改为单向)
- Replace Magic Number with Symbolic Constant(以符号常量/字面常量取代魔法数)
- Encapsulate Field(封装值域)
- Encapsulate Collection(封装群集)
- Replace Record with Data Class(以数据类取代记录)
- Replace Type Code with Class(以类取代型别码)
- Replace Type Code with Subclasses(以子类取代型别码)
- Replace Type Code with State/Strategy(以State/strategy 取代型别码)
- Replace Subclass with Fields(以值域取代子类)
- 章节九 简化条件表达式
- Decompose Conditional(分解条件式)
- Consolidate Conditional Expression(合并条件式)
- Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
- Remove Control Flag(移除控制标记)
- Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件式)
- Replace Conditional with Polymorphism(以多态取代条件式)
- Introduce Null Object(引入Null 对象)
- Introduce Assertion(引入断言)
- 章节十一 处理概括关系
- Pull Up Field(值域上移)
- Pull Up Method(函数上移)
- Pull Up Constructor Body(构造函数本体上移)
- Push Down Method(函数下移)
- Push Down Field(值域下移)
- Extract Subclass(提炼子类)
- Extract Superclass(提炼超类)
- Extract Interface(提炼接口)
- Collapse Hierarchy(折叠继承关系)
- Form Template Method(塑造模板函数)
- Replace Inheritance with Delegation(以委托取代继承)
- Replace Delegation with Inheritance(以继承取代委托)
- 章节十二 大型重构
- 这场游戏的本质
- Tease Apart Inheritance(梳理并分解继承体系)
- Convert Procedural Design to Objects(将过程化设计转化为对象设计)
- Separate Domain from Presentation(将领域和表述/显示分离)
- Extract Hierarchy(提炼继承体系)
- 章节十三 重构,复用与现实
- 现实的检验
- 为什么开发者不愿意重构他们的程序?
- 现实的检验(再论)
- 重构的资源和参考资料
- 从重构联想到软件复用和技术传播
- 结语
- 参考文献
- 章节十四 重构工具
- 使用工具进行重构
- 重构工具的技术标准(Technical Criteria )
- 重构工具的实用标准(Practical Criteria )
- 小结
- 章节十五 集成
- 参考书目