Что такое ковариантность в ООП?

Ответ

Ковариантность — это возможность использовать более конкретный тип (подтип) вместо более общего (родительского) в определённых контекстах. В объектно-ориентированном программировании это часто относится к возвращаемым типам методов.

Пример в PHP (начиная с версии 7.4):

class Animal {}
class Dog extends Animal {}

interface AnimalShelter {
    public function adopt(): Animal;
}

class DogShelter implements AnimalShelter {
    // Возвращаем Dog, который является подтипом Animal — это ковариантность.
    public function adopt(): Dog {
        return new Dog();
    }
}

Ключевые аспекты:

  • Направление: Работает с возвращаемыми значениями методов. Дочерний класс может сужать (уточнять) тип возвращаемого значения по сравнению с родительским методом.
  • Типобезопасность: Гарантирует, что возвращаемое значение будет совместимо с ожидаемым родительским типом, повышая надёжность кода.
  • Контравариантность: Противоположное понятие — возможность использовать более общий тип для параметров метода. В PHP на данный момент поддерживается только ковариантность возвращаемых типов и контравариантность типов аргументов.

На практике это позволяет писать более выразительный и строгий код, когда специализированные классы точно указывают, с каким конкретным типом они работают.

Ответ 18+ 🔞

Давай разжую эту тему, чтобы она перестала казаться какой-то мартышлюшкой из высшей математики. Представь, что ты заходишь в зоомагазин.

Объявляется: «У нас есть сеть приютов AnimalShelter. Из любого такого приюта ты гарантированно получишь Animal — ну, какое-то животное». Это наш контракт, интерфейс.

interface AnimalShelter {
    public function adopt(): Animal;
}

Теперь открывается конкретный приют — DogShelter. И тут вступает ковариантность, ёпта. По старой логике, этот приют тоже должен был бы орать: «Мы выдаём Animal!». А ты приходишь, а тебе — собака. Ну и что? Собака — это ведь животное? Естественно, блядь. Так зачем врать?

Ковариантность — это как раз разрешение не врать. Она говорит: «Ладно, чувак, раз ты DogShelter, то можешь в своём методе adopt() прямо и заявить — «я возвращаю не просто Animal, а конкретно Dog». Потому что Dog — это подтип Animal, и это никого не обломает, а только уточнит».

class DogShelter implements AnimalShelter {
    // Раньше тут была бы ошибка, а теперь — красота. Говорим прямо: верну собаку.
    public function adopt(): Dog {
        return new Dog();
    }
}

Что тут важно понять, чтобы не охуеть потом:

  • Направление движения: Ковариантность работает на возврате из метода. Дочерний класс может сузить (уточнить) тип возвращаемого значения. Dog вместо Animal. Это логично: если обещали животное, а дали собаку — все довольны, волнение ебать нулевое.
  • Безопасность: Вся эта затея — типобезопасная. PHP не даст тебе в DogShelter написать public function adopt(): Potato. Потому что картошка — не животное. Компилятор (интерпретатор) тебя нахуй пошлёт сразу, и это правильно.
  • Антонимчик — контравариантность: Это уже про параметры методов. Если ковариантность — «я верну что-то более конкретное», то контравариантность — «я приму что-то более общее». В PHP это тоже работает, но это уже отдельная песня. Главное — не путай их, а то в голове будет хуй с винтом.

Итог: Ковариантность возвращаемых типов — это не какая-то пиздопроебибная теория. Это реальный инструмент, чтобы твой код перестал мычать что-то общее «я верну object» и начал чётко заявлять: «я верну UserDTO». Код становится строже, понятнее, и IDE тебя лучше понимает. А если IDE понимает — значит, и ты сам меньше шансов накосячить. Всем плюс, терпения ноль ебать на расплывчатые типы.