### Extract Superclass(提炼超类)
两个classes 有相似特性(similar features)。
为这两个classes 建立一个superclass ,将相同特性移至superclass 。
![](https://box.kancloud.cn/2016-08-15_57b1b5e71c4c6.gif)
**动机(Motivation)**
重复代码是系统中最主要的一种糟糕东西。如果你在不同的地方进行相同一件事 情,一旦需要修改那些动作时,你就得负担比你原本应该负担的更多事情。
重复代码的某种形式就是:两个classes 以相同的方式做类似的事情,或者以不同的方式做类似的事情。对象提供了一种简化这种情况的机制,那就是继承机制。但是,在建立这些具有共通性的classes 之前,你往往无法发现这样的共通性,因此你经常会在「具有共通性」的classes 存在之后,再幵始建立其间的继承结构。
另一种选择就是Extract Class。这两种方案之间的选择其实就是继承(Inheritance )和委托(delegation)之间的选择。如果两个classes 可以共享行为, 也可以共享接口,那么继承是比较简单的作法。如果你选错了,也总有 Replace Inheritance with Delegation 这瓶后悔药可吃。
**作法(Mechanics)**
- 为原本的classes 新建一个空白的abstract superclass。
- 运用Pull Up Field, Pull Up Method, 和 Pull Up Constructor Body 逐一将subclass 的共同充素上移到superclass 。
- 先搬移值域,通常比较简单。
- 如果相应的subclass 函数有不同的签名式(signature),但用途相同,可以先使用Rename Method 将它们的签名式改为相同,然后 再使用 Pull Up Method。
- 如果相应的subclass 函数有相同的签名式,但函数本体不同,可以在superclass 中把它们的共同签名式声明为抽象函数。
- 如果相应的subclass 函数有不同的函数本体,但用途相同,可试着使用 Substitute Algorithm 把其中一个函数的函数本体拷贝到另一个函数中。如果运转正常,你就可以使用 Pull Up Method。
- 每次上移后,编译并测试。
- 检查留在subclass 中的函数,看它们是否还有共通成分。如果有,可以使用Extract Method 将共通部分再提炼出来,然后使用 Pull Up Method 将提炼出的函数上移到superclass 。如果各个subclass 中某个函数的整体流程很相似,你也许可以使用Form Template Method。
- 将所有共通元素都上移到superclass 之后,检查subclass 的所有用户。如果它们只使用共同接口,你就可以把它们所索求的对象型别改为superclass 。
**范例:(Example)**
下面例子中,我以Employee 表示「员工」,以Department 表示「部门」:
~~~
class Employee...
public Employee (String name, String id, int annualCost) {
_name = name;
_id = id;
_annualCost = annualCost;
}
public int getAnnualCost() {
return _annualCost;
}
public String getId(){
return _id;
}
public String getName() {
return _name;
}
private String _name;
private int _annualCost;
private String _id;
public class Department...
public Department (String name) {
_name = name;
}
public int getTotalAnnualCost(){
Enumeration e = getStaff();
int result = 0;
while (e.hasMoreElements()) {
Employee each = (Employee) e.nextElement();
result += each.getAnnualCost();
}
return result;
}
public int getHeadCount() {
return _staff.size();
}
public Enumeration getStaff() {
return _staff.elements();
}
public void addStaff(Employee arg) {
_staff.addElement(arg);
}
public String getName() {
return _name;
}
private String _name;
private Vector _staff = new Vector();
~~~
这里有两处共同点。首先,员工和部门都有名称(names);其次,它们都有年度成本(annual costs),只不过计算方式略有不同。我要提炼出一个superclass ,用以包容这些共通特性。第一步是新建这个superclass ,并将现有的两个classes 定义为其subclasses:
~~~
abstract class Party {}
class Employee extends Party...
class Department extends Party...
~~~
然后我开始把特性上移至superclass 。先实施Pull Up Field 通常会比较简单:
~~~
class Party...
protected String _name;
~~~
然后,我可以使用 Pull Up Method 把这个值域的取值函数(getter)也上移至superclass :
~~~
class Party {
public String getName() {
return _name;
}
~~~
我通常会把这个值域声明为private 。不过,在此之前,我需要先使用Pull Up Constructor Body,这样才能对_name 正确赋值:
~~~
class Party...
protected Party (String name) {
_name = name;
}
private String _name;
class Employee...
public Employee (String name, String id, int annualCost) {
super (name);
_id = id;
_annualCost = annualCost;
}
class Department...
public Department (String name) {
super (name);
}
~~~
Department.getTotalAnnualCost() 和 Employee.getAnnualCost() 两个函数的用途相同,因此它们应该有相同的名称。我先运用 Rename Method 把它们的名称改为相同:
~~~
class Department extends Party {
public int getAnnualCost(){
Enumeration e = getStaff();
int result = 0;
while (e.hasMoreElements()) {
Employee each = (Employee) e.nextElement();
result += each.getAnnualCost();
}
return result;
}
~~~
它们的函数本体仍然不同,因此我目前还无法使用 Pull Up Method。但是我 可以在superclass 中声明一个抽象函数:
~~~
abstract public int getAnnualCost()
~~~
这一步修改完成后,我需要观察两个subclasses 的用户,看看是否可以改变它们转而使用新的superclass 。用户之一就是Department 自身,它保存了一个Employee 对象群集。Department .getAnnualCost() 只调用群集内的元素(对象)的getAnnualCost() 函数,而该函数此刻乃是在Party class 声明的:
~~~
class Department...
public int getAnnualCost(){
Enumeration e = getStaff();
int result = 0;
while (e.hasMoreElements()) {
Party each = (Party) e.nextElement();
result += each.getAnnualCost();
}
return result;
}
~~~
这一行为暗示一种新的可能性:我可以用Composite 模式[Gang of Four] 来对待Department 和Employee ,这样就可以让一个Department 对象包容另—个Department 对象。这是一项新功能,所以这项修改严格来说不属于重构范围。如果用户恰好需要Composite 模式,我可以修改_staff 值域名字,使其更好地表现这一模式。这一修改还会带来其他相应修改:修改addStaff() 函数名称,并将该函数的参数型别改为Party class 。最后还需要把headCount() 函数变成一个递归调用。我的作法是在Employee 中建立一个headCount() 函数,让它返回1;再使用Substitute Algorithm 修改Department 的headCount() 函数,让它总和(add)各部门的headCount() 调用结果。
- 译序 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 )
- 小结
- 章节十五 集成
- 参考书目