返回

类型派生的万花筒——TypeScript的协变与逆变

前端

引言

面向对象编程的一个重要概念是类型派生,它允许你创建新的类型,这些类型继承了另一个类型的属性和方法。TypeScript中的类型派生也是如此,你可以使用extends来创建一个子类型,子类型继承了父类型的所有属性和方法。

但是,在某些情况下,你可能会希望子类型能够访问父类型没有的方法或属性。这就是协变和逆变的用武之地。协变允许你将子类型的参数类型更改为更宽泛的类型,而逆变允许你将子类型的返回值类型更改为更窄的类型。

协变

协变是一种类型派生,允许你将子类型的参数类型更改为更宽泛的类型。换句话说,子类型可以接受比父类型更多的参数。

例如,假设你有一个父类Animal和一个子类DogAnimal类有一个eat()方法,它接受一个Food类型的参数。Dog类继承了Animal类,因此它也有一个eat()方法。但是,Dog类的eat()方法可以接受一个更宽泛的类型,比如Meat类型。

class Animal {
  eat(food: Food) {
    console.log('Animal is eating food.');
  }
}

class Dog extends Animal {
  eat(meat: Meat) {
    console.log('Dog is eating meat.');
  }
}

在这个例子中,Dog类的eat()方法可以接受Meat类型的参数,而Animal类的eat()方法只能接受Food类型的参数。这是因为Meat类型是Food类型的子类型,因此Meat类型的参数也可以传递给Food类型的参数。

协变在TypeScript中有很多用处。例如,你可以使用协变来创建更通用的函数,这些函数可以接受不同的参数类型。你还可以使用协变来创建更灵活的类,这些类可以处理不同的数据类型。

逆变

逆变是一种类型派生,允许你将子类型的返回值类型更改为更窄的类型。换句话说,子类型可以返回比父类型更少的数据。

例如,假设你有一个父类Animal和一个子类DogAnimal类有一个speak()方法,它返回一个string类型的值。Dog类继承了Animal类,因此它也有一个speak()方法。但是,Dog类的speak()方法可以返回一个更窄的类型,比如string类型的子类型Woof类型。

class Animal {
  speak(): string {
    return 'Animal is speaking.';
  }
}

class Dog extends Animal {
  speak(): Woof {
    return 'Woof!';
  }
}

在这个例子中,Dog类的speak()方法可以返回Woof类型的返回值,而Animal类的speak()方法只能返回string类型的返回值。这是因为Woof类型是string类型的子类型,因此Woof类型的返回值也可以传递给string类型的返回值。

逆变在TypeScript中也有很多用处。例如,你可以使用逆变来创建更通用的函数,这些函数可以返回不同的数据类型。你还可以使用逆变来创建更灵活的类,这些类可以处理不同的数据类型。

协变与逆变的局限性

协变和逆变虽然非常有用,但它们也有一定的局限性。协变只能用于参数类型,不能用于返回值类型。逆变只能用于返回值类型,不能用于参数类型。

此外,协变和逆变只能用于类型派生,不能用于其他类型的类型转换。例如,你不能将一个协变类型的变量赋值给一个逆变类型的变量。

结语

协变和逆变是TypeScript中的两个重要概念,可以让你更灵活地使用类型。协变允许你将子类型的参数类型更改为更宽泛的类型,而逆变允许你将子类型的返回值类型更改为更窄的类型。协变和逆变在TypeScript中有很多用处,但它们也有一定的局限性。