### Move Method(搬移函数)
(译注:本节大量保留class,method,source,target等字眼)
你的程序中,有个函数与其所驻class之外的另一个class进行更多交流:调用后者,或被后者调用。
在该函数最常引用(指涉)的class中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数(delegating method),或是将旧函数完全移除。
![](https://box.kancloud.cn/2016-08-15_57b1b56be692d.gif)
**动机(Motivation)**
「函数搬移」是重构理论的支柱。如果一个class有太多行为,或如果一个class与另一个class有太多合作而形成高度耦合(highly coupled),我就会搬移函数。通过这种手段,我可以使系统中的classes更简单,这些classes最终也将更干净利落地实现系统交付的任务。
常常我会浏览class的所有函数,从中寻找这样的函数:使用另一个对象的次数比使用自己所驻对象的次数还多。一旦我移动了一些值域,就该做这样的检查。一旦发现「有可能被我搬移」的函数,我就会观察调用它的那一端、它调用的那一端,以及继承体系中它的任何一个重定义函数。然后,我会根据「这个函数与哪个对象的交流比较多」,决定其移动路径。
这往往不是一个容易做出的决定。如果不能肯定是否应该移动一个函数,我就会继续观察其他函数。移动其他函数往往会让这项决定变得容易一些。有时候,即使你移动了其他函数,还是很难对眼下这个函数做出决定。其实这也没什么大不了的。 如果真的很难做出决定,那么或许「移动这个函数与否」并不那么重要。所以,我会凭本能去做,反正以后总是可以修改的。
**作法(Mechanics)**
- 检查source class定义之source method所使用的一切特性(features),考虑它们是否也该被搬移。(译注:此处所谓特性泛指class定义的所有东西,包括值域和函数。)
- 如果某个特性只被你打算搬移的那个函数用到,你应该将它一并搬移。如果另有其他函数使用了这个特性,你可以考虑将使用该特性的所有函数全都一并搬移。有时候搬移一组函数比逐一搬移简单些。
- 检查source class的subclass和superclass,看看是否有该函数的其他声明。
- 如果出现其他声明,你或许无法进行搬移,除非target class也同样表现出多态性(polylmorphism〕。
- 在target class中声明这个函数。
- 你可以为此函数选择一个新名称——对target class更有意义的名称。
- 将source method的代码拷贝到target method中。调整后者,使其能在新家中正常运行。
- 如果target method使用了source特性,你得决定如何从target method引用source object。如果target class中没有相应的引用机制,就把source object reference当作参数,传给新建立的target class。
- 如果source method包含异常处理式(exception handler),你得判断逻辑上应该由哪个来处理这一异常。如果应该由source class负责,就把异常处理式留在原地。
- 编译target class。
- 决定如何从source正确引用target object。
- 可能会有一个现成的值域或函数帮助你取得target class。如果没有,就看能否轻松建立一个这样的函数。如果还是不行,你得在source class中新建一个新值域来保存target object。这可能是一个永久性修改,但你也可以让它保持暂时的地位,因为后继的其他重构项目可能会把这个新建值域去掉。
- 修改source method,使之成为一个delegating method(纯委托函数〕。
- 编译,测试。
- 决定「删除source method」或将它当作一个delegating method保留下来。
- 如果你经常要在source object中引用target method,那么将source method作为delegating method保留下来会比较简单。
- 如果你想移除source method,请将source class中对source method的所有引用动作,替换为「对target method的引用动作」。
- 编译,测试。
**范例(Examples)**
我用一个表示「帐户」的account class来说明这项重构:
~~~
class Account...
double overdraftCharge() { //译注:透支金计费,它和其他class的关系似乎比较密切。
if (_type.isPremium()) {
double result = 10;
if (_daysOverdrawn > 7) result += (_daysOverdrawn - 7) * 0.85;
return result;
}
else return _daysOverdrawn * 1.75;
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) result += overdraftCharge();
return result;
}
private AccountType _type;
private int _daysOverdrawn;
~~~
假设有数种新帐户,每一种都有自己的「透支金计费规则」。所以我希望将overdraftCharge()搬移到AccountType class去。
第一步要做的是:观察被overdraftCharge()使用的每一特性(features),考虑是否值得将它们与overdraftCharge()—起移动。此例之中我需要让daysOverdrawn值域留在Account class,因为其值会随不同种类的帐户而变化。然后,我将overdraftCharge()函数码拷贝到AccountType中,并做相应调整。
~~~
class AccountType...
double overdraftCharge(int daysOverdrawn) {
if (isPremium()) {
double result = 10;
if (daysOverdrawn > 7) result += (daysOverdrawn - 7) * 0.85;
return result;
}
else return daysOverdrawn * 1.75;
}
~~~
在这个例子中,「调整」的意思是:(1)对于「使用AccountType特性」的语句,去掉_type;(2)想办法得到依旧需要的Account class特性。当我需要使用source class特性,我有四种选择:(1)将这个特性也移到target class;(2)建立或使用一个从target class到source的引用〔指涉)关系;(3)将source object当作参数传给target class;(4)如果所需特性是个变量,将它当作参数传给target method。
本例中我将_daysOverdrawn变量作为参数传给target method(上述(4))。
调整target method使之通过编译,而后我就可以将source method的函数本体替换为一个简单的委托动作(delegation),然后编译并测试:
~~~
class Account...
double overdraftCharge() {
return _type.overdraftCharge(_daysOverdrawn);
}
~~~
我可以保留代码如今的样子,也可以删除source method。如果决定删除,就得找出source method的所有调用者,并将这些调用重新定向,改调用Account的bankCharge():
~~~
class Account...
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) result += _type.overdraftCharge(_daysOverdrawn);
return result;
}
~~~
所有调用点都修改完毕后,我就可以删除source method在Account中的声明了。我可以在每次删除之后编译并测试,也可以一次性批量完成。如果被搬移的函数不是private,我还需要检查其他classes是否使用了这个函数。在强型(strongly typed) 语言中,删除source method声明式后,编译器会帮我发现任何遗漏。
此例之中被移函数只取用(指涉〕一个值域,所以我只需将这个值域作为参数传给target method就行了。如果被移函数调用了Account中的另一个函数,我就不能这么简单地处理。这种情况下我必须将source object传递给target method:
~~~
class AccountType...
double overdraftCharge(Account account) {
if (isPremium()) {
double result = 10;
if (account.getDaysOverdrawn() > 7)
result += (account.getDaysOverdrawn() - 7) * 0.85;
return result;
}
else return account.getDaysOverdrawn() * 1.75;
}
~~~
如果我需要source class的多个特性,那么我也会将source object传递给target method。不过如果target method需要太多source class特性,就得进一步重构。通常这种情况下我会分解target method,并将其中一部分移回source class。
- 译序 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 )
- 小结
- 章节十五 集成
- 参考书目