Ковариантност
Covariance allows a child’s method to return a more specific type than the return type of its parent’s method.
Whereas, contravariance allows a parameter type to be less specific in a child method, than that of its parent.
Ако си представим една йерархична структура от наследяващи се класове, covariance означава, че „децата“ връщат по-конкретен тип резултат.
По-просто, covariance „затяга конкретността“, contravariance – обратното.
Какво значи „more specific“ (по-голяма конкретност)?
1. когато не се използват т.н. union types (изброени с „|“ типове);
2. ако класът, който използваме за тип, има родител, не използваме родителя;
3. вместо float, използваме int (ако случаят го позволява разбира се), защото int е частен случай на float.
Както се вижда, това е основната логика зад понятието „ковариантност“.
Ако обърнем логиката обратно – към „разширяване“ или „намаляване на конкретността“ – това пък е „контравариантност“.
Ето много прост пример за ковариантност:
<?php
abstract class Animal
{
public function __construct(protected string $name) { }
abstract public function speak();
}
class Dog extends Animal
{
public function speak(): void
{
echo $this->name . " barks";
}
}
class Cat extends Animal
{
public function speak(): void
{
echo $this->name . " meows";
}
}
// Lest have a simple factory, that produces cats or dogs...
function makeMeADog(): Dog
{
return new Dog('Bobi');
}
function makeMeACat(): Cat
{
return new Cat('Pisko');
}
Къде е ковариантността?
Можем да променим Animal да не е абстрактен, а нормален клас и да си „произвеждаме“ животни.
Но решаваме да сме по-конкретни, искаме не Животно, а конкретно Куче, Котка… и… това е.
Контравариантност
Пример за контравариантност би бил ако например в някой от оверрайдващите методи, зададем като тип на входен параметър – по-малко конкретен тип. Тоест, ако наследникът „разшири“ вместо да „затегне“ конкретността.
class Food { }
class AnimalFood extends Food { }
abstract class Animal
{
public function __construct(protected string $name) { }
public function eat(AnimalFood $food): void
{
echo $this->name . " eats " . get_class($food);
}
}
class Dog extends Animal
{
public function eat(Food $food): void
{
echo $this->name . " eats " . get_class($food);
}
}
Вижда се как в Dog, наследникът на Animal, в оверрайдващият метод eat() позволяваме входен параметър Food с по-малка конкретност.
Това ще да е някое всеядно куче 😉
Литература: