企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
![](https://box.kancloud.cn/6983d32b7d3042fc3eebe8c2cc874590_468x433.jpeg) ``` class Department {  get totalAnnualCost() {...}  get name() {...}  get headCount() {...} } class Employee {  get annualCost() {...}  get name() {...}  get id() {...} } ``` ![](https://box.kancloud.cn/a3bed334e2e1f6d1a46c5039deb25af9_91x152.jpeg) ``` 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; } ```