Композиция вместо Наследования в PHP
Прежде чем мы рассмотрим преимущества Композиции над Наследованием, давайте разберёмся в этих двух принципах независимо друг от друга.
Наследование
Это фундаментальная концепция ООП в PHP, представляющая собой технику, при которой объект или класс основывается на другом объекте (прототипическое наследование) или классе (наследование на основе класса), используя одну и ту же реализацию и дополняя её возможностью использовать, расширять и изменять поведение, определённое в других классах. Типичным примером является класс Vehicle
, имеющий такие подклассы, как Car
и Motorcycle
, где последние наследуют свойства и методы класса Vehicle
. Сила наследования заключается в его простоте и в том, что оно облегчает повторное использование кода, однако при неправильном использовании оно может привести к созданию жёстко связанных и негибких структур кода, известных как иерархии наследования (Inheritance Hierarchies).
Композиция
С другой стороны, Композиция — это ещё одна практика ООП, при которой класс формируется из нескольких более простых классов или объектов путём их объединения и сборки таким образом, чтобы смоделировать более сложное поведение. Вместо того чтобы создавать монолитные классы с наследуемыми атрибутами и методами, композиция работает за счёт содержания экземпляров других классов, реализующих требуемую функциональность.
Используя предыдущий пример, вместо класса Car
, наследующего от Vehicle
, мы можем иметь класс Vehicle
, содержащий экземпляры Engine
, Wheels
и других необходимых компонентов. Такая организация обеспечивает высокую степень гибкости, позволяя более точно контролировать поведение объектов.
Композиция вместо Наследования
Принцип Композиция вместо Наследования — это рекомендация как можно чаще использовать Композицию вместо Наследования. Причина в том, что хотя наследование позволяет определить поведение класса в терминах другого класса, оно связывает жизненный цикл и уровень доступа родительского класса с дочерним классом, что может сделать код более жёстким. Композиция позволяет объектам получать поведение и возможности через отношения, а не через жёсткую структуру классов, что приводит к лучшей модульности и меньшему сцеплению между классами.
Использование Композиции вместо Наследования даёт более надёжное решение при разработке сложных приложений с большим количеством подвижных частей и поведений. Такой подход обеспечивает гибкость и масштабируемость, а также позволяет получить дизайн, в большей степени соответствующий реальному миру.
Ниже мы рассмотрим пример рефакторинга кода, использующего Наследование, для использования вместо него Композиции. Цель состоит не в том, чтобы пренебречь наследованием, а в том, чтобы показать, как композиция может обеспечить более надёжное и гибкое решение в определённых сценариях.
Рефакторинг от Наследования к Композиции
Представьте себе, что у нас есть система каталогизации и классификации птиц. Мы могли бы иметь что-то вроде этого:
// Пример Наследования
class Bird
{
public function fly(): string
{
return "I can fly!";
}
}
final class Pigeon extends Bird
{
}
$pigeon = new Pigeon();
echo $pigeon->fly(); // I can fly!
В приведённом примере мы имеем базовый класс Bird
, содержащий метод fly()
. Класс Pigeon
наследуется от Bird
и, следовательно, имеет доступ к методу fly()
. Однако такой подход может стать проблематичным, если мы захотим добавить класс Penguin
. Пингвины технически являются птицами, но они не летают.
final class Penguin extends Bird
{
}
$penguin = new Penguin();
echo $penguin->fly(); // I can fly!
Это можно исправить, переопределив метод fly(). Но в зависимости от размера и сложности проекта это может выйти из-под контроля. Мы можем доработать эту реализацию, чтобы вместо неё использовать Композицию:
// Пример Композиции
final class Bird
{
public function __construct(
protected BirdType $birdType,
) {}
public function fly(): string
{
return $this->birdType->fly();
}
}
interface BirdType
{
public function fly(): string;
}
final class FlyingBird implements BirdType
{
public function fly(): string
{
return "I can fly!";
}
}
final class NonFlyingBird implements BirdType
{
public function fly(): string
{
return "I can't fly!";
}
}
$pigeon = new Bird(new FlyingBird());
echo $pigeon->fly(); // I can fly!
$penguin = new Bird(new NonFlyingBird());
echo $penguin->fly(); // I can't fly!
В приведённом выше примере мы переработали наш код для использования Композиции. Теперь у нас есть интерфейс BirdType
, обозначающий, может ли птица летать или нет, а класс Bird принимает объект BirdType
в качестве аргумента своего конструктора. Теперь класс Bird
имеет метод fly()
, указывающий на метод fly()
объекта BirdType
. Такой подход позволяет получить больший контроль над отдельными функциями поведения, поскольку каждая функциональность инкапсулируется в своём классе. Это изменение позволяет нам добиться большей гибкости (теперь наш пингвин не может летать), а класс Bird
обладает лучшей масштабируемостью, что облегчает управление уникальным поведением различных типов птиц.
Благодаря Композиции каждый класс несёт свою собственную ответственность (Принцип единой ответственности), а сложные модели поведения лучше управляются путём разделения их на отдельные сущности. Взаимодействие классов друг с другом становится более явным, и в итоге мы получаем структуру кода, которую легче поддерживать и расширять, избегая жёсткой структуры наследования.
Заключение
В области ООП обе концепции являются жизненно важными инструментами в инструментарии
разработчика. В этой статье я попытался показать преимущество Композиции над Наследованием. Хотя Наследование — это действительно интуитивный и элегантный способ организации и повторного использования кода, в зависимости от контекста и способа реализации оно может стать ограничивающим и проблематичным, когда классы становятся тесно связанными и слишком зависимыми от жёсткой иерархической структуры.
При разработке реальных PHP-приложений обычно используются сложные классы с многочисленными поведенческими функциями. Как показано в примере, использование Композиции вместо Наследования делает код более надёжным, масштабируемым и управляемым. Это приводит к тому, что код становится проще расширять и поддерживать, усиливая модульность и инкапсуляцию.
Помните, что выбор правильной техники или концепции в значительной степени зависит от конкретного контекста и общих требований к проекту. Цель данной статьи — не игнорировать наследование, а понять, как Композиция обеспечивает гибкую альтернативу в конкретных ситуациях.
Использование Композиции в качестве инструмента первого класса наряду с Наследованием позволяет получить более широкий спектр решений проблем проектирования программного обеспечения, что приводит к созданию надёжных и поддерживаемых PHP-приложений.
Надеюсь, что Вам понравилась эта статья, и если да, то не забудьте поделиться этой статьёй со своими друзьями!!! До встречи!