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() {...}
}动机
如果我看见两个类在做相似的事,可以利用基本的继承机制把它们的相似之处提炼到超类。我可以用字段上移(353))把相同的数据搬到超类,用函数上移(350)搬移相同的行为。
很多技术作家在谈到面向对象时,认为继承必须预先仔细计划,应该根据“真实世界”的分类结构建立对象模型。真实世界的分类结构可以作为设计继承关系的提示,但还有很多时候,合理的继承关系是在程序演化的过程中才浮现出来的:我发现了一些共同元素,希望把它们抽取到一处,于是就有了继承关系。
另一种选择就是提炼类(182)。这两种方案之间的选择,其实就是继承和委托之间的选择,总之目的都是把重复的行为收拢一处。提炼超类通常是比较简单的做法,所以我会首选这个方案。即便选错了,也总有以委托取代超类(399)这瓶后悔药可吃。
做法
为原本的类新建一个空白的超类。
如果需要的话,用改变函数声明(124)调整构造函数的签名。
测试。
使用构造函数本体上移(355)、函数上移(350)和字段上移(353))手法,逐一将子类的共同元素上移到超类。
检查留在子类中的函数,看它们是否还有共同的成分。如果有,可以先用提炼函数(106)将其提炼出来,再用函数上移(350)搬到超类。
检查所有使用原本的类的客户端代码,考虑将其调整为使用超类的接口。
范例
下面这两个类,仔细考虑之下,是有一些共同之处的——它们都有名字(name),也都有月度成本(monthly cost)和年度成本(annual cost)的概念:
class Employee {
constructor(name, id, monthlyCost) {
this._id = id;
this._name = name;
this._monthlyCost = monthlyCost;
}
get monthlyCost() {return this._monthlyCost;}
get name() {return this._name;}
get id() {return this._id;}
get annualCost() {
return this.monthlyCost * 12;
}
}
class Department {
constructor(name, staff){
this._name = name;
this._staff = staff;
}
get staff() {return this._staff.slice();}
get name() {return this._name;}
get totalMonthlyCost() {
return this.staff
.map(e => e.monthlyCost)
.reduce((sum, cost) => sum + cost);
}
get headCount() {
return this.staff.length;
}
get totalAnnualCost() {
return this.totalMonthlyCost * 12;
}
}可以为它们提炼一个共同的超类,更明显地表达出它们之间的共同行为。
首先创建一个空的超类,让原来的两个类都继承这个新的类。
class Party {}
class Employee extends Party {
constructor(name, id, monthlyCost) {
super();
this._id = id;
this._name = name;
this._monthlyCost = monthlyCost;
}
// rest of class...
class Department extends Party {
constructor(name, staff){
super();
this._name = name;
this._staff = staff;
}
// rest of class...在提炼超类时,我喜欢先从数据开始搬移,在 JavaScript 中就需要修改构造函数。我先用字段上移(353))把 name 字段搬到超类中。
class Party…
constructor(name){
this._name = name;
}class Employee…
constructor(name, id, monthlyCost) {
super(name);
this._id = id;
this._monthlyCost = monthlyCost;
}class Department…
constructor(name, staff){
super(name);
this._staff = staff;
}把数据搬到超类的同时,可以用函数上移(350)把相关的函数也一起搬移。首先是 name 函数:
class Party…
get name() {return this._name;}class Employee…
get name() {return this._name;}class Department…
get name() {return this._name;}有两个函数实现非常相似。
class Employee…
get annualCost() {
return this.monthlyCost * 12;
}class Department…
get totalAnnualCost() {
return this.totalMonthlyCost * 12;
}它们各自使用的函数 monthlyCost 和 totalMonthlyCost 名字和实现都不同,但意图却是一致。我可以用改变函数声明(124)将它们的名字统一。
class Department…
get totalAnnualCost() {
return this.monthlyCost * 12;
}
get monthlyCost() { ... }然后对计算年度成本的函数也做相似的改名:
class Department…
get annualCost() {
return this.monthlyCost * 12;
}现在可以用函数上移(350)把这个函数搬到超类了。
class Party…
get annualCost() {
return this.monthlyCost * 12;
}class Employee…
get annualCost() {
return this.monthlyCost * 12;
}class Department…
get annualCost() {
return this.monthlyCost * 12;
}