(译) PECS 原理

(译) PECS 原理

PECS来自集合的观点。如果你只是从通用集合中得到对象,那么它是生产者,
你应该使用extends; 如果你只是添加对象,它是一个消费者,你应该使用super。
如果同时使用相同的集合,则不应使用extends或super。

假设你有一个方法,它将事物的集合作为参数,但你希望它比仅接受一个更灵活Collection

案例1:你希望浏览集合并对每个项目执行操作。

然后列表是生产者,所以你应该使用Collection<? extends Thing>。

原因是Collection<? extends Thing>可以保存任何子类型Thing,每个Thing类型的对象在执行操作时都这样。
(你实际上无法向a添加任何内容Collection<? extends Thing>,因为你无法在运行时知道该集合的哪个特定子类型Thing。)

案例2:你想要向集合中添加内容。

然后列表是消费者,所以你应该使用Collection<? super Thing>。

这里的推理是不同的Collection<? extends Thing>,无论实际的参数化类型是什么,Collection<? super Thing>都可以随时保持Thing。
在这里你不关心列表中已有的内容,只要它允许Thing添加; 这是什么? super Thing保证。

在一门程序设计语言的类型系统中,一个类型规则或者类型构造器是:

  • 协变(covariant),如果它保持了子类型序关系≦。该序关系是:子类型≦基类型。
  • 逆变(contravariant),如果它逆转了子类型序关系。
  • 不变(invariant),如果上述两种均不适用。

Animal[]并不是总能当作Cat[],因为当一个客户读取数组并期望得到一个Cat,但Animal[]中包含的可能是个Dog。所以逆变规则是不安全,所以如果get一个被斜变修饰的类型返回的是object类型

stackOverFlow image

返回值的协变

在允许协变返回值的语言中, 子类可以重写 getAnimalForAdoption 方法来返回一个更窄的类型:

1
2
3
4
5
class CatShelter extends AnimalShelter {
Cat getAnimalForAdoption() {
return new Cat();
}
}

方法参数的逆变

类似地,子类重写的方法接受更宽的类型也是类型安全(type safe)的:

1
2
3
4
5
class CatShelter extends AnimalShelter {
void putAnimal(Object animal) {
...
}
}

协变的方法参数类型

在主流的语言中,Eiffel 允许一个重写的方法参数比起父类中的那一个有更加具体的类型,即参数类型协变。因此,Eiffel 版本的 putAnimal 会如下所示:

1
2
3
4
5
class CatShelter extends AnimalShelter {
void putAnimal(Cat animal) {
...
}
}

去除对参数类型协变的依赖

其它语言特性可能用来弥补缺乏参数类型协变的缺乏。

在有泛型(即参数化多态及受限量词)的语言中,前面的例子可用更类型安全的方式重写[5] :不定义 AnimalShelter,改为定义一个参数化的类 Shelter。(这种方法的缺点之一是基类实现者需要预料到哪些类型要在子类中特化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Shelter<T extends Animal> {
T getAnimalForAdoption() {
...
}

void putAnimal(T animal) {
...
}
}


class CatShelter extends Shelter<Cat> {
Cat getAnimalForAdoption() {
...
}

void putAnimal(Cat animal) {
...
}
}

参考文档

What is PECS (Producer Extends Consumer Super)?

维基百科-逆变与协变

#

评论

`
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×