TypeScript 中的类实现接口、接口继承接口、接口继承类

7,885 阅读5分钟

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

类实现接口

前面我们学习过 TS 中的接口,有两种作用:

  • 面向对象编程——行为的抽象
  • 描述对象的形状

现在我们就来学习接口和类之间的关系。

首先最基本的就是类实现接口。

那么接口是什么?接口就是将一些相似的类的共同的属性和方法抽象出来,比如下面的例子:

class Bird {
  fly() {
    console.log('鸟在飞');
  }
  jump() {
    console.log('鸟在跳');
  }
}
class Bee {
  fly() {
    console.log('蜜蜂在飞');
  }
  honey() {
    console.log('蜜蜂在采蜜');
  }
}

Bird 这个类,有 fly 和 jump 这两个方法;Bee 这个类,有 fly 和 honey 这两个方法,我们可以把公有的方法抽象成一个接口。我们使用 interface 来定义一个接口 Wings,这个接口定义了一个方法 fly。

接下来,我们使用 implements 来将鸟和蜜蜂,都来实现这个翅膀接口。 这样的好处,就是我们可以在统一的地方进行约束这个接口的类型,然后如果将来有其他的类,需要满足这种接口的话,可以直接实现它,这样就可以约束实现这个接口的类,都拥有了统一的行为。

interface Wings {
  fly(): void;
}

class Bird implements Wings {
  fly() {
    console.log('鸟在飞');
  }
  jump() {
    console.log('鸟在跳');
  }
}
class Bee implements Wings {
  fly() {
    console.log('蜜蜂在飞');
  }
  honey() {
    console.log('蜜蜂在采蜜');
  }
}

需要注意的是,你的类可以先继承另一个类,然后再实现接口,并且可以同时实现多个接口。

我们来看下面的例子:

interface Wings {
  fly(): void;
}
interface Mouth {
  sing(): void;
}

abstract class Animal {
  abstract eat(): void;
}

class Bird extends Animal implements Wings, Mouth {
  fly() {
    console.log('鸟在飞');
  }
  eat() {
    console.log('eating');
  }
  sing() {
    console.log('singing');
  }
}

这里的接口和抽象类的用法,比较类似,都是定义了一些公共的方法,需要具体的类去实现。

他们的区别在于:

接口就像一个插件一样,是用来增强类的;而抽象类,是具体类的抽象概念,比如这里的Bird 就是动物的一种。

另一个区别是,类实现接口,是一个多对多的关系,一个类可以实现多个接口,一个接口,也可以被多个类实现。

而类继承类,是一个一对多的关系,一个类的父类,只能有一个,而一个类的子类,可以有多个。

习题:根据已声明的接口,下面用法正确的是?

interface animalBehavior {
  getFood(): string;
}
interface animalInfo {
  setAge(age: number): void;
}

// A
class Animal extends animalInfo {
  private age = 10;
  setAge(age: number) {
    this.age = age;
  }
}

// B
class Animal implements animalInfo, animalBehavior {
  private age = 10;
  setAge(age: number) {
    this.age = age;
  }
}

// C
class Animal implements animalInfo {
  private age = 10;
  setAge(age: number) {
    this.age = age;
  }
}

// D
class Animal implements animalInfo, animalBehavior {
  private age = 10;
  private food = 'bamboo';
  setAge(age: number) {
    this.age = age;
  }
  getFood() {
    return this.food;
  }
}

答案:

C D

解析:

类可以使用 implements 关键字实现接口,类需要实现接口中的所有方法。

  • A - 类使用 implements 实现接口而不是 extends。故错误。
  • B - 类 Animal 同时继承了接口 animalInfo 和接口 animalBehavior,但是只实现了接口 animalInfosetAge 方法,并未实现接口 animalBehaviorgetFood 方法,故错误。
  • C - 类 Animal 继承了接口 animalInfo,并实现了接口 animalInfo 中的方法 setAge,用法正确。
  • D - 类 Animal 同时继承了接口 animalInfo 和接口 animalBehavior,并实现了接口中的所有方法,用法正确。

接口继承接口

在 TS 中接口这个概念非常灵活,除了类可以实现接口之外,一个接口也可以继承另一个接口,比如下面的例子:

interface Mouth {
  sing(): void;
}
// 另一个接口继承前面的 Mouth
interface DragonMouth extends Mouth {
  fire(): void;
}

class Dragon implements DragonMouth {
  sing() {
    console.log('龙在唱歌');
  }
  fire() {
    console.log('龙在喷火');
  }
}

习题:已知下面代码块,接口 animal 具体为?

interface animalInfo {
  setAge(age: number): void;
}
interface animalBehavior {
  getFood(): string;
}
interface animal extends animalInfo, animalBehavior {
  setFeature(feature: string): void;
}


// A
interface animal {
  setAge(age: number): void;
}

// B
interface animal {
  setFeature(feature: string): void;
}

// C
interface animal {
  setAge(age: number): void;
  getFood(): string;
}

// D
interface animal {
  setAge(age: number): void;
  getFood(): string;
  setFeature(feature: string): void;
}

答案:

D

解析:

接口可以使用 extends 关键字继承其他接口,一个接口可以继承多个接口。

题目中接口 animal 同时继承了接口 animalInfo 和接口 animalBehavior,所以接口 animal 应该为

interface animal {
    setAge(age: number): void;
    getFood(): string;
    setFeature(feature: string): void;
}

接口继承类

一个接口除了可以继承另一个接口之外,它可以继承一个类,比如下面的例子:

class Dragon {
  fly() {
    console.log('龙在飞');
  }
}

interface FireDragon extends Dragon {
  fire(): void;
}

let f: FireDragon = {
  fire: function() {
    console.log('龙在喷火');
  },
  fly() {
    console.log('龙在飞');
  }
}

习题:已知下面代码块,使用正确的是?

class AnimalInfo {
  public age = 10;
  public setAge(age: number) {
    this.age = age;
  }
}

interface animal extends AnimalInfo {
  getFood(): string;
}

// A
const panda: animal = {
  age: 20,
  getFood: () => {
    return 'bamboo';
  },
  setAge: (age: number) => {}
}

// B
const panda: animal = {
  age: 20,
  getFood: () => {
    return 'bamboo';
  }
}

// C
const panda: animal = {
  getFood: () => {
    return 'bamboo';
  },
  setAge: (age: number) => {}
}

// D
class panda: animal{
  public age = 20;
  public setAge(age: number) {
    this.age = age;
  };
  public getFood() {
    return 'bamboo';
  } 
}

答案:A

解析:

接口可以使用 extends 继承类。

题目中的接口 animal 继承自类 AnimalInfo。我们可以理解为 animal 表示的接口为

interface animal {
    age: number;
    setAge: (age: number) => void;
    getFood: () => void;
}

所以答案选 AD 是错误的语法哦!

资料:构造函数的类型

在 TypeScript 中,我们可以用 interface 来描述类,同时也可以用 interface 里特殊的 new() 来描述类的构造函数类型。使用方法如下:

// 用 INumber 来描述 NumberClass 的形状
interface INumber {
    n: number;
    double(n: number): number
}
// 构造一个 NumberClass 类
class NumberClass implements INumber {
    constructor(public n: number) {
        console.log('n is ' + n)
    }
    double(n: number) {
        return 2 * n;
    }
}

// 用 new() 来描述 NumberClass 的构造函数类型
interface INumberClassConstructable {
    new(n: number): INumber
}

// 该方法接受一个类,并将之实例化,利用 INumberClassConstructable 就可以约束实例化时传递给 clazz 构造函数的参数是准确的
function buildNumber(clazz: INumberClassConstructable) {
    return new clazz(1)
}