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