协变与逆变
在 TypeScript 中,函数的参数和返回值类型涉及到协变(covariance)和逆变(contravariance)的概念。这些概念有助于理解类型系统的灵活性和安全性。下面是对协变和逆变的详细解释,并通过示例来帮助你更好地理解它们。
协变(Covariance)
协变指的是子类型可以替换父类型。在 TypeScript 中,这通常应用于函数的返回值类型。具体来说,如果一个函数返回的类型是另一个类型的子类型,那么这个函数可以被视为与其返回值类型为父类型的函数兼容。
示例
假设我们有两个类型 Animal
和 Dog
,其中 Dog
是 Animal
的子类型:
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
在这个例子中:
ReturnsDog
是ReturnsAnimal
的子类型,因为Dog
是Animal
的子类型。- 因此,
getDog
可以赋值给animalGetter
,这符合协变原则。
逆变(Contravariance)
逆变指的是父类型可以替换子类型。在 TypeScript 中,这通常应用于函数的参数类型。具体来说,如果一个函数接受的参数类型是另一个类型的父类型,那么这个函数可以被视为与其参数类型为子类型的函数兼容。
示例
继续使用之前的 Animal
和 Dog
类型:
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()); // 合法
在这个例子中:
TakesAnimal
是TakesDog
的超类型,因为Animal
是Dog
的父类型。- 因此,
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
,因为Dog
是Animal
的子类型,返回值类型符合协变规则。
总结
- 协变(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 的类型系统来编写更安全和灵活的代码。