В PHP типы параметров и возвращаемых значений могут быть изменены в дочерних классах относительно родительских. Это известно как ковариантность и контравариантность.
- Ковариантность — «сужает» тип — позволяет использовать более конкретный тип, чем тот, который определен в родительском классе — уточняет тип возвращаемого значения.
- Контравариантность — «расширять» тип — наоборот, позволяет использовать более общий тип. Снижает требования для входных параметров.
Давайте рассмотрим коваринтность и контравариантность на примере PHP кода.
Ковариантность
Ковариантность — это концепция в типизации языка программирования, связанная с подстановкой типов в иерархии наследования. Она позволяет использовать более конкретный тип (подтип) вместо более общего (супертипа).
Пример ковариантности
Предположим, у нас есть два класса: Animal
и Cat
, где Cat
является подклассом Animal
. У обоих есть метода get()
, возвращаемый тип в Animal
объявленный как Animal
, мы можем использовать Cat
в дочернем классе Cat
, поскольку Cat
является подтипом Animal
.
Это допустимо, потому что Cat
является подтипом Animal
(т.е., Cat
является Animal
), поэтому это является примером ковариантности.
class Animal {
public function get(): Animal {
return $this;
}
}
class Cat extends Animal {
public function get(): Cat {
return $this;
}
}
Отметим, что в PHP 7.2.0 была добавлена частичная контравариантность путём устранения ограничений типа для параметров в дочернем методе.
Начиная с PHP 7.4.0, добавлена полная поддержка ковариантности и контравариантности.
Объявление типа считается конкретным в следующем случае
Удалено объединение типов.
class Base
{
public function getNumber(): int|float|string {
//....
}
}
class Child extends Base {
public function getNumber(): int|float {
//....
}
}
Добавлено пересечение типов
Iterator -> Iterator&Countable
Тип класса изменяется на тип дочернего класса
class Animal {
public function get(): Animal {
return $this;
}
}
class Cat extends Animal {
public function get(): Cat {
return $this;
}
}
iterable изменён на массив (array) или Traversable
В противном случае класс типа считается менее конкретным.
Преимущества
Ковариантность в типизации позволяет сделать код более гибким и удобочитаемым, предоставляя преимущества, которые способствуют лучшей модульности и скорости разработки:
- Интуитивно понятные иерархии наследования: Когда дочерний класс может возвращать тип, являющийся подтипом типа, возвращаемого его родительским классом, иерархии наследования становятся более естественными и интуитивно понятными.
- Большая гибкость: Ковариантность даёт возможность более точного указания возвращаемого типа в дочерних классах, что позволяет более гибко управлять кодом.
- Лучшая поддержка полиморфизма: Возможность заменять типы применяется при использовании принципа подстановки Лискова (LSP), который является ключевым принципом в ООП. Это означает, что дочерний класс должен иметь возможность заменить родительский класс без изменения корректности программы.
- Улучшение безопасности типов: Вы получаете преимущества статической типизации, что упрощает чтение и понимание кода, а также помогает избежать определенных ошибок.
- Упрощение рефакторинга кода: Использование ковариантности может сделать процесс рефакторинга более безопасным и менее подверженным ошибкам, поскольку вам не нужно беспокоиться о нарушении иерархии наследования при изменении типа возвращаемого значения.