Какие альтернативы наследованию существуют
Что проверяют
Понимаете ли вы, почему глубокие иерархии классов плохо переживают изменения, и чем в PHP принято их заменять. Обычно следом спрашивают, чем трейт отличается от интерфейса и когда наследование всё-таки уместно.
Как отвечать
Главный принцип: предпочитайте композицию наследованию. Наследование жёстко связывает потомка с деталями родителя, и правка базового класса ломает всех наследников. Альтернативы:
- композиция: объект получает зависимости через конструктор и пользуется ими. С promoted properties из PHP 8.0 это одна строка:
public function __construct(private readonly Mailer $mailer) {}; - интерфейсы: контракт без реализации, класс может реализовать сколько угодно интерфейсов. Зависимость объявляется интерфейсом, и реализацию подменяет DI-контейнер или тест;
- делегирование: класс переадресует вызовы вложенному объекту. На этом построены «декоратор» (кеширующая обёртка над репозиторием) и «стратегия» (разные способы расчёта доставки за одним интерфейсом);
- трейты: горизонтальное переиспользование кода, методы трейта встраиваются в класс при компиляции. Конфликты имён решаются через
insteadofиas; - агрегация: вариант композиции, где вложенный объект живёт сам по себе, например отдел хранит список сотрудников, но не создаёт их.
Уместное наследование тоже стоит назвать: настоящее отношение «является», абстрактный класс с шаблонным методом, точки расширения фреймворка. Хороший признак: классы по умолчанию final, а наследование разрешается осознанно.
Где спотыкаются
Называют трейты множественным наследованием. Трейт не создаёт тип: его нельзя указать как тип параметра и проверить через instanceof, поэтому контракт всё равно описывают интерфейсом.
Выносят в трейт состояние и зависимости. Трейт начинает знать про свойства класса-хозяина и превращается в скрытую связанность, которая хуже наследования.
Делают композицию через new внутри конструктора. Зависимость остаётся жёсткой, подменить её в тесте нельзя, и выигрыш от отказа от наследования пропадает.