### Pull Up Method(函数上移)
有些函数,在各个subclass 中产生完全相同的结果。
将该函数移至superclass。
![](https://box.kancloud.cn/2016-08-15_57b1b5aa64f65.gif)
**动机(Motivation)**
避免「行为重复」是很重要的。尽管「重复的两个函数」也可以各自工作得很好, 但「重复」自身会成为错误的滋生地,此外别无价值。无论何时,只要系统之内出现重复,你就会面临「修改其中一个却未能修改另一个」的风险。通常,找出重复也有一定困难。
如果某个函数在各subclass 中的函数体都相同(它们很可能是通过「拷贝-粘贴」得到的),这就是最显而易见的Pull Up Method 适用场合。当然,情况并不总是如此明显。你也可以只管放心地重构,再看看测试程序会不会发牢骚,但这就需要对你的测试有充分的信心。我发现,观察这些可疑(可能重复的〕函数之间的差异往往大有收获:它们经常会向我展示那些我忘记测试的行为。
Pull Up Method 常常紧随其他重构而被使用。也许你能找出若干个「身处不 同subclasses 内的函数」而它们又可以「通过某种形式的参数调整」而后成为相同函数。这时候,最简单的办法就是首先分别调整这些函数的参数,然后再将它们概括(generalize)到superclass中。当然,如果你自信足够,也可以一次同时完成这两个步骤。
有一种特殊情况也需要使用Pull Up Method : subclass 的函数覆写(overrides) 了superclass 的函数,但却仍然做相同的工作。
Pull Up Method 过程中最麻烦的一点就是:被提升的函数可能会引用「只出现于subclass 而不出现于superclass」的特性。如果被引用的是个函数,你可以将该函数也一同提升到superclass,或者在superclass 中建立一个抽象函数。在此过程中,你可能需要修改某个函数的签名式(signature),或建立一个委托函数(delegating method)。
如果两个函数相似但不相同,你或许可以先以Form Template Method 构造出相同的函数,然后再提升它们。
**作法(Mechanics)**
- 检查「待提升函数」,确定它们是完全一致的(identical)。
- 如果这些函数看上去做了相同的事,但并不完全一致,可使用Substitute Algorithm 让它们变得完全一致。
- 如果「待提升函数」的签名式(signature)不同,将那些签名式都修改为你想要在superclass 中使用的签名式。
- 在superclass 中新建一个函数,将某一个「待提升函数」的代码拷贝到其中,做适当调整,然后编译。
- 如果你使用的是一种强型(strongly typed)语言,而「待提升函数」 又调用了一个「只出现于subclass 未出现于superclass」的函数,你可以在superclass 中为被调用函数声明一个抽象函数。
- 如果「待提升函数」使用了 subclass 的一个值域,你可以使用Pull Up Field 将该值域也提升到superclass;或者也可以先使用 Self Encapsulate Field,然后在superclass 中把取值函数(getter)声明为抽象函数。
- 移除一个「待提升的subclass 函数」。
- 编译,测试。
- 逐一移除「待提升的如函数」,直到只剩下superclass 中的函数为止。每次移除之后都需要测试。
- 观察该函数的调用者,看看是否可以将它所索求的对象型别改为superclass。
**范例:(Example)**
我以Customer「表示「顾客」,它有两个subclass :表示「普通顾客」的RegularCustomer 和表示「贵宾」PreferredCustomer。
![](https://box.kancloud.cn/2016-08-15_57b1b5e674fa9.gif)
两个subclass 都有一个createBill() 函数,并且代码完全一样:
~~~
void createBill (date Date) {
double chargeAmount = charge (lastBillDate, date);
addBill (date, charge);
}
~~~
但我不能直接把这个函数上移到superclass,因为各个subclass 的chargeFor() 函数并不相同。我必须先在superclass 中声明chargeFor() 抽象函数:
~~~
class Customer...
abstract double chargeFor(date start, date end)
~~~
然后,我就可以将createBill() 函数从其中一个subclass 拷贝到superclass。拷贝完之后应该编译,然后移除那个subclass 的createBill() 函数,然后编译并测试。 随后再移除另一个subclass 的createBill() 函数,再次编译并测试:
![](https://box.kancloud.cn/2016-08-15_57b1b5e699f6b.gif)
- 译序 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 )
- 小结
- 章节十五 集成
- 参考书目