跳到主要内容

协变与逆变

在 TypeScript 中,函数的参数和返回值类型涉及到协变(covariance)和逆变(contravariance)的概念。这些概念有助于理解类型系统的灵活性和安全性。下面是对协变和逆变的详细解释,并通过示例来帮助你更好地理解它们。

协变(Covariance)

协变指的是子类型可以替换父类型。在 TypeScript 中,这通常应用于函数的返回值类型。具体来说,如果一个函数返回的类型是另一个类型的子类型,那么这个函数可以被视为与其返回值类型为父类型的函数兼容。

示例

假设我们有两个类型 AnimalDog,其中 DogAnimal 的子类型:

class Animal {}
class Dog extends Animal {}

// 定义两个函数类型
type ReturnsAnimal = () => Animal;
type ReturnsDog = () => Dog;

// 函数返回 Dog 类型,这是 Animal 的子类型
const getDog: ReturnsDog = () => new Dog();

// 协变:ReturnsDog 可以赋值给 ReturnsAnimal
const animalGetter: ReturnsAnimal = getDog; // 合法

// 使用 animalGetter 获取动物实例
const animal: Animal = animalGetter(); // 返回的是 Dog 实例,但类型为 Animal

在这个例子中:

  • ReturnsDogReturnsAnimal 的子类型,因为 DogAnimal 的子类型。
  • 因此,getDog 可以赋值给 animalGetter,这符合协变原则。

逆变(Contravariance)

逆变指的是父类型可以替换子类型。在 TypeScript 中,这通常应用于函数的参数类型。具体来说,如果一个函数接受的参数类型是另一个类型的父类型,那么这个函数可以被视为与其参数类型为子类型的函数兼容。

示例

继续使用之前的 AnimalDog 类型:

class Animal {}
class Dog extends Animal {}

// 定义两个函数类型
type TakesAnimal = (animal: Animal) => void;
type TakesDog = (dog: Dog) => void;

// 函数接受 Animal 类型的参数,这是 Dog 的父类型
const takeAnimal: TakesAnimal = (animal: Animal) => {
console.log('Taking an animal');
};

// 逆变:TakesAnimal 可以赋值给 TakesDog
const dogTaker: TakesDog = takeAnimal; // 合法

// 使用 dogTaker 传递 Dog 实例
dogTaker(new Dog()); // 合法

在这个例子中:

  • TakesAnimalTakesDog 的超类型,因为 AnimalDog 的父类型。
  • 因此,takeAnimal 可以赋值给 dogTaker,这符合逆变原则。

协变与逆变的组合

在实际应用中,函数的参数和返回值类型可能会同时涉及协变和逆变。TypeScript 默认情况下对函数参数类型是逆变的,而对返回值类型是协变的。

示例

结合协变和逆变:

class Animal {}
class Dog extends Animal {}

// 定义函数类型
type AnimalProcessor = (animal: Animal) => Animal;
type DogProcessor = (dog: Dog) => Dog;

// 函数接受 Animal 类型的参数并返回 Animal 类型
const processAnimal: AnimalProcessor = (animal: Animal) => {
return new Animal();
};

// 逆变:AnimalProcessor 可以赋值给 DogProcessor
const dogProcessor: DogProcessor = processAnimal; // 不合法

// 正确的逆变和协变组合
const correctDogProcessor: DogProcessor = (dog: Dog) => {
return new Dog();
};

// 协变:correctDogProcessor 可以赋值给 AnimalProcessor
const animalProcessor: AnimalProcessor = correctDogProcessor; // 合法

console.log(animalProcessor(new Dog())); // 输出: Dog {}

在这个例子中:

  • processAnimal 接受 Animal 并返回 Animal,不能直接赋值给 DogProcessor,因为 DogProcessor 需要严格匹配 Dog 类型。
  • correctDogProcessor 接受 Dog 并返回 Dog,可以赋值给 AnimalProcessor,因为 DogAnimal 的子类型,返回值类型符合协变规则。

总结

  • 协变(Covariance): 子类型可以替换父类型,适用于函数的返回值类型。
  • 逆变(Contravariance): 父类型可以替换子类型,适用于函数的参数类型。
  • 默认行为:
    • 参数类型是逆变的。
    • 返回值类型是协变的。

具体代码示例

以下是一个完整的示例,展示了协变和逆变的具体用法:

// 定义基类和派生类
class Animal {
makeSound() {
console.log('Some generic sound');
}
}

class Dog extends Animal {
makeSound() {
console.log('Bark');
}

fetch() {
console.log('Fetching the ball');
}
}

// 定义函数类型
type ReturnsAnimal = () => Animal;
type ReturnsDog = () => Dog;

type TakesAnimal = (animal: Animal) => void;
type TakesDog = (dog: Dog) => void;

// 协变示例
const getDog: ReturnsDog = () => new Dog();
const animalGetter: ReturnsAnimal = getDog; // 协变:ReturnsDog 可以赋值给 ReturnsAnimal

// 使用 animalGetter 获取动物实例
const animal: Animal = animalGetter(); // 返回的是 Dog 实例,但类型为 Animal
animal.makeSound(); // 输出: Bark

// 逆变示例
const takeAnimal: TakesAnimal = (animal: Animal) => {
console.log('Taking an animal');
};

const dogTaker: TakesDog = takeAnimal; // 逆变:TakesAnimal 可以赋值给 TakesDog

// 使用 dogTaker 传递 Dog 实例
dogTaker(new Dog()); // 输出: Taking an animal

// 组合示例
const processAnimal: AnimalProcessor = (animal: Animal) => {
return new Animal();
};

// 错误示例:AnimalProcessor 不能赋值给 DogProcessor
// const dogProcessor: DogProcessor = processAnimal; // 不合法

// 正确的逆变和协变组合
const correctDogProcessor: DogProcessor = (dog: Dog) => {
return new Dog();
};

// 协变:correctDogProcessor 可以赋值给 AnimalProcessor
const animalProcessor: AnimalProcessor = correctDogProcessor; // 合法

console.log(animalProcessor(new Dog())); // 输出: Dog {}

详细注释

// 定义基类和派生类
class Animal {
makeSound() {
console.log('Some generic sound'); // 定义 Animal 类的 makeSound 方法
}
}

class Dog extends Animal {
makeSound() {
console.log('Bark'); // 覆盖 Animal 类的 makeSound 方法
}

fetch() {
console.log('Fetching the ball'); // 定义 Dog 类的 fetch 方法
}
}

// 定义函数类型
type ReturnsAnimal = () => Animal; // 定义 ReturnsAnimal 类型,返回 Animal 类型
type ReturnsDog = () => Dog; // 定义 ReturnsDog 类型,返回 Dog 类型

type TakesAnimal = (animal: Animal) => void; // 定义 TakesAnimal 类型,接受 Animal 类型参数
type TakesDog = (dog: Dog) => void; // 定义 TakesDog 类型,接受 Dog 类型参数

// 协变示例
const getDog: ReturnsDog = () => new Dog(); // 定义 getDog 函数,返回 Dog 实例
const animalGetter: ReturnsAnimal = getDog; // 协变:ReturnsDog 可以赋值给 ReturnsAnimal

// 使用 animalGetter 获取动物实例
const animal: Animal = animalGetter(); // 返回的是 Dog 实例,但类型为 Animal
animal.makeSound(); // 输出: Bark // 调用 makeSound 方法,输出 "Bark"

// 逆变示例
const takeAnimal: TakesAnimal = (animal: Animal) => {
console.log('Taking an animal'); // 定义 takeAnimal 函数,接受 Animal 类型参数
};

const dogTaker: TakesDog = takeAnimal; // 逆变:TakesAnimal 可以赋值给 TakesDog

// 使用 dogTaker 传递 Dog 实例
dogTaker(new Dog()); // 输出: Taking an animal // 调用 takeAnimal 函数,输出 "Taking an animal"

// 组合示例
const processAnimal: AnimalProcessor = (animal: Animal) => {
return new Animal(); // 定义 processAnimal 函数,接受 Animal 类型参数并返回 Animal 实例
};

// 错误示例:AnimalProcessor 不能赋值给 DogProcessor
// const dogProcessor: DogProcessor = processAnimal; // 不合法 // 注释掉错误示例

// 正确的逆变和协变组合
const correctDogProcessor: DogProcessor = (dog: Dog) => {
return new Dog(); // 定义 correctDogProcessor 函数,接受 Dog 类型参数并返回 Dog 实例
};

// 协变:correctDogProcessor 可以赋值给 AnimalProcessor
const animalProcessor: AnimalProcessor = correctDogProcessor; // 合法

console.log(animalProcessor(new Dog())); // 输出: Dog {} // 调用 animalProcessor 函数,输出 Dog 实例

总结

  • 协变(Covariance): 子类型可以替换父类型,适用于函数的返回值类型。
  • 逆变(Contravariance): 父类型可以替换子类型,适用于函数的参数类型。
  • 默认行为:
    • 参数类型是逆变的。
    • 返回值类型是协变的。

通过理解协变和逆变,你可以更好地利用 TypeScript 的类型系统来编写更安全和灵活的代码。