继承

已知一些矩阵是对称的。 如果沿左上角到右下角的对角线翻转对称矩阵,它保持不变。 换句话说,存储在x,y的值总是与y,x相同。

想象一下,我们需要一个像Matrix这样的数据结构,但是它必需保证一个事实,矩阵是对称的。 我们可以从头开始编写它,但这需要重复一些代码,与我们已经写过的代码很相似。

JavaScript 的原型系统可以创建一个新类,就像旧类一样,但是它的一些属性有了新的定义。 新类派生自旧类的原型,但为set方法增加了一个新的定义。

在面向对象的编程术语中,这称为继承(inheritance)。 新类继承旧类的属性和行为。

  1. class SymmetricMatrix extends Matrix {
  2. constructor(size, element = (x, y) => undefined) {
  3. super(size, size, (x, y) => {
  4. if (x < y) return element(y, x);
  5. else return element(x, y);
  6. });
  7. }
  8. set(x, y, value) {
  9. super.set(x, y, value);
  10. if (x != y) {
  11. super.set(y, x, value);
  12. }
  13. }
  14. }
  15. let matrix = new SymmetricMatrix(5, (x, y) => `${x},${y}`);
  16. console.log(matrix.get(2, 3));
  17. // → 3,2

extends这个词用于表示,这个类不应该直接基于默认的Object原型,而应该基于其他类。 这被称为超类(superclass)。 派生类是子类(subclass)。

为了初始化SymmetricMatrix实例,构造器通过super关键字调用其超类的构造器。 这是必要的,因为如果这个新对象的行为(大致)像Matrix,它需要矩阵具有的实例属性。 为了确保矩阵是对称的,构造器包装了content方法,来交换对角线以下的值的坐标。

set方法再次使用super,但这次不是调用构造器,而是从超类的一组方法中调用特定的方法。 我们正在重新定义set,但是想要使用原来的行为。 因为this.set引用新的set方法,所以调用这个方法是行不通的。 在类方法内部,super提供了一种方法,来调用超类中定义的方法。

继承允许我们用相对较少的工作,从现有数据类型构建稍微不同的数据类型。 它是面向对象传统的基础部分,与封装和多态一样。 尽管后两者现在普遍被认为是伟大的想法,但继承更具争议性。

尽管封装和多态可用于将代码彼此分离,从而减少整个程序的耦合,但继承从根本上将类连接在一起,从而产生更多的耦合。 继承一个类时,比起单纯使用它,你通常必须更加了解它如何工作。 继承可能是一个有用的工具,并且我现在在自己的程序中使用它,但它不应该成为你的第一个工具,你可能不应该积极寻找机会来构建类层次结构(类的家族树)。