我想解释为什么协变和逆变很重要,以及为什么它们分别适用于返回类型和参数类型,而不是相反。
协变可能最容易理解,并且与 Liskov 替换原则直接相关。使用上面的例子,假设我们收到一个 `AnimalShelter` 对象,然后我们想通过调用它的 `adopt()` 方法来使用它。我们知道它返回一个 `Animal` 对象,无论该对象到底是什么,即它是 `Cat` 还是 `Dog`,我们都可以以相同的方式对待它们。因此,专门化返回类型是可以的:我们至少知道任何可以返回的东西的通用接口,并且我们可以以相同的方式对待所有这些值。
逆变稍微复杂一些。它与提高方法灵活性的实用性密切相关。再次使用上面的例子,也许“基础”方法 `eat()` 接受一种特定类型的食物;但是,_特定_动物可能希望支持_更广泛_的食物类型。也许它,就像上面的例子一样,为原始方法添加了功能,允许它消耗_任何_种类的食物,而不仅仅是为动物准备的食物。`Animal` 中的“基础”方法已经实现了允许它消耗为动物专门设计的食物的功能。`Dog` 类中的覆盖方法可以检查参数是否为 `AnimalFood` 类型,并简单地调用 `parent::eat($food)`。如果参数_不是_专门类型的,它可以对该参数执行额外的甚至完全不同的处理 - 不会破坏原始签名,因为它_仍然_处理专门类型,但也处理更多。这就是为什么它也与 Liskov 替换密切相关:消费者仍然可以将专门的食物类型传递给 `Animal`,而不知道它到底是 `Cat` 还是 `Dog`。