第十二章:处理继承关系
一、函数上移
动机:
避免重复代码是很重要的。
如果某个函数在各个子类中的函数体都相同,这就是显而易见的函数上移适合场景。
具体展现:
// 重构前
class Employee { ... }
class Salesman extends Employee {
get name() { ... }
}
class Enginner extends Employee {
get name() { ... }
}
// 重构后
class Employee {
get name() { ... }
}
class Salesman extends Employee { ... }
class Enginner extends Employee { ... }
二、字段上移
动机:
其实跟函数上移一样,只不过变成了重复的特性或字段等。
具体展现:
// 重构前
class Employee { ... } // Java
class Salesman extends Employee {
private String name;
}
class Enginner extends Employee {
private String name;
}
// 重构后
class Employee {
private String name;
}
class Salesman extends Employee { ... }
class Enginner extends Employee { ... }
三、构造函数本体上移
动机:
构造函数是很奇妙的东西。它们不是普通函数,使用它们比使用普通函数受到更多的限制。它们附加了特殊的规则,对一些做法与函数的调用次序有所限制。
如果构造过程过于复杂,可以考虑以工厂函数取代构造函数。
具体展现:
// 重构前
class Party { ... }
class Employee extends Party {
constructor(name, id, monthlyCost) {
super();
this._id = id;
this._name = name;
this._monthlyCost = monthlyCost;
}
}
// 重构后
class Party {
constructor(name) {
this._name = name;
}
}
class Employee extends Party {
constructor(name, id, monthlyCost) {
super(name);
this._id = id;
this._monthlyCost = monthlyCost;
}
}
四、函数下移
动机:
如果超类中的某个函数只与一个(或少于几个)子类有关,那么最好将其从超类中移走,放到真正关心它的子类中去。
具体展现:
// 重构前
class Employee {
get quota() { ... }
}
class Enginner extends Employee { ... }
class Salesman extends Employee { ... }
// 重构后
class Employee { ... }
class Enginner extends Employee { ... }
class Salesman extends Employee {
get quota() { ... }
}
五、字段下移
动机:
同上。
具体展现:
// 重构前
class Employee { //Java
protected String quota;
}
class Enginner extends Employee { ... }
class Salesman extends Employee { ... }
// 重构后
class Employee { ... }
class Enginner extends Employee { ... }
class Salesman extends Employee {
protected String quota;
}
六、以子类取代类型码
动机:
表现分类关系的第一种工具是类型码字段——根据具体的编程语言,可以分为枚举、符号、字符串或者数字。大多数时候,有这样的类型码就足够了。但也有些时候,我们可以更进一步,引入子类。继承有两个诱人之处:一是,可以用多态来处理条件逻辑。二是,有些字段或函数只对特定的类型码取值才有意义。
具体展现:
// 重构前
class createEmployee(name, type) {
return new Emplpyee(name, type);
}
// 重构后
function createEmployee(name, type) {
switch (type) {
case "engineer": return new Engineer(name);
case "salesman": return new Salesman(name);
case "manager": return new Manager(name);
}
}
七、移除子类
动机:
随着软件的演化,子类所支持的变化可能会被搬移到别处,甚至完全去除,这时子类就失去了价值。子类存在着就有成本,阅读者需要花心思去理解它的用意,所以如果子类的用处太少,就不值得存在,此时最好的选择就是移除子类,将其替换为超类中的一个字段。
具体展现:
// 重构前
class Person {
get genderCode() { return "X"; }
}
class Male extends Person {
get genderCode() { return "M"; }
}
class Female extends Person {
get genderCode() { return "F"; }
}
// 重构后
class Person {
get genderCode() { return this._genderCode; }
}
八、提炼超类
动机:
如果看到两个类在做相似的事,可以利用基本的继承机制把他们的相似之处提炼到超类。
很多时候合理的继承关系是在程序演化的过程中才浮现出来的:我发现了一些共同元素,希望把它们抽取到一处,于是就有了继承关系。
具体展现:
// 重构前
class Department {
get totalAnnualCost() { ... }
get name() { ... }
get headCount() { ... }
}
class Employee {
get annualCost() { ... }
get name() { ... }
get id() { ... }
}
// 重构后
class Party {
get name() { ... }
get annualCost() { ... }
}
class Department extends Party {
get annualCost() { ... }
get headCount() { ... }
}
class Employee extends Party {
get annualCost() { ... }
get id() { ... }
}
九、折叠继承体系
动机:
在重构类继承体系时,我们经常把函数和字段上下移动。随着继承体系的演化,我们有时会发现一个类与其超类已经没有多大差别,不值得再作为独立的类存在。此时就可以把超类和子类合并起来。
具体展现:
// 重构前
class Employee { ... }
class Salesman extends Employee { ... }
// 重构后
class Employee { ... }
十、以委托取代子类
动机:
继承也有其短板。最明显的是:继承这张牌只能打一次。例如:可以是“年轻人”或“老人”、“富人”或“穷人”,但不能同时采用两种继承方式。另外更大的问题在于继承给类之间引入了非常紧密的关系。在超类上的任何修改,都很可能会破坏子类。
这两个问题,委托都能解决。委托是对象之间常规的关系。与继承关系相比,使用委托关系时接口更清晰、耦合更少。
具体展现:
// 重构前
class Order {
get daysToShip() {
return this._warehouse.daysToShip;
}
}
class PriorityOrder extends Order {
get daysToShip() {
return this._priorityPlan.daysToShip;
}
}
// 重构后
class Order {
get daysToShip() {
return (this._priorityDelegate)
? this._priorityDelegate.daysToShip;
: this._warehouse.daysToShip;
}
}
class PriorityOrderDelegate {
get daysToShip() {
return this._priorityPlan.daysToShip;
}
}
十一、以委托取代超类
动机:
如果超类的一些函数对子类并不使用,就说明我们不应该通过继承来获得超类的功能。
合理的继承关系还有一个重要特征:子类的所有实例都应该是超类的实例,通过超类的接口来使用子类的实例应该完全不出问题。
其实这个重构方法跟上边那个“以委托取代子类”本质上是一样的,只不过取代的对象是超类中不适合子类的内容。
具体展现:
// 重构前
class List { ... }
class Stack extends List { ... }
// 重构后
class Stack {
constructor() {
this._storage = new List();
}
}
class List { ... }