Трейты и наследование
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 55 из 91.
Теперь, когда вы понимаете, как работают трейты, давайте проясним, когда использовать трейты, а когда — наследование. Оба подхода позволяют повторно использовать код, но они служат разным целям и обладают различными характеристиками.
Наследование устанавливает отношение «является» (is-a). Класс Dog, расширяющий Animal, означает, что собака является животным. Дочерний класс наследует идентичность родителя и может использоваться везде, где ожидается родительский тип.
Трейты обеспечивают возможность «has-a» (обладания). Когда класс использует трейт Loggable, он обладает функциональностью логирования — но он не становится логгером. Трейты добавляют поведение, не меняя того, чем класс является по своей сути.
<?php
// Наследование: Dog ЯВЛЯЕТСЯ Animal
class Animal {
public function breathe(): void {
echo "Breathing";
}
}
class Dog extends Animal {
public function bark(): void {
echo "Woof!";
}
}
// Трейт: Dog ОБЛАДАЕТ возможностью логирования
trait Loggable {
public function log(string $msg): void {
echo "Log: $msg";
}
}
class Cat extends Animal {
use Loggable;
}
Основные отличия, которые следует помнить:
| Аспект | Наследование | Трейты |
|---|---|---|
| Отношение | является (is-a) | обладает возможностью (has-a) |
| Ограничение | Только один родитель | Разрешено несколько трейтов |
| Проверка типов | Работает с instanceof | Нет идентификации типа |
| Лучше всего подходит для | Иерархические отношения | Общее поведение для несвязанных классов |
Используйте наследование, когда классы имеют общую идентичность. Используйте трейты, когда несвязанным классам требуется одинаковая функциональность, не являясь при этом частью одного дерева иерархии.
Задание
ЛегкоДавайте создадим систему транспортных средств, которая продемонстрирует, когда использовать наследование, а когда — трейты. Вы создадите иерархию транспортных средств, которые разделяют общую идентичность через наследование, добавляя при этом сквозные возможности через трейты.
Вы организуете свой код в четырех файлах:
Trackable.php— Создайте трейт с названиемTrackable, который обеспечивает возможность GPS-отслеживания. Трейт должен иметь:- Приватное свойство
$location(string) - Метод
setLocation(string $location), который сохраняет текущее местоположение - Метод
getLocation(), который возвращает"Location: " . $this->location
- Приватное свойство
Vehicle.php— Создайте базовый классVehicle, который устанавливает отношение «является» (is-a). Подключите файл трейта. Используйте продвижение параметров конструктора (constructor promotion) для определения защищенного свойства$brand. Добавьте методdescribe(), который возвращает"Vehicle: " . $this->brand. Этот класс должен использовать трейтTrackable, так как все транспортные средства в нашем парке будут иметь GPS.Car.php— Создайте классCar, который расширяетVehicle. Подключите файл Vehicle. Используйте продвижение параметров конструктора для принятия бренда и приватного свойства$doors(integer). Вызовите родительский конструктор, передав бренд. Переопределите методdescribe(), чтобы он возвращал"Car: " . $this->brand . " with " . $this->doors . " doors".main.php— Подключите файл Car. Вы получите три входных значения: название бренда, количество дверей и строку местоположения. Создайте экземплярCar, установите его местоположение, а затем выведите две строки:- Результат вызова
describe() - Результат вызова
getLocation()
- Результат вызова
Обратите внимание, как наследование и трейты работают здесь вместе: Car является Vehicle (наследование устанавливает идентичность), в то время как Vehicle имеет возможность отслеживания (трейт добавляет поведение). Вы могли бы использовать instanceof Vehicle, чтобы проверить, является ли объект транспортным средством, но не существует типа «Trackable» для проверки — в этом и заключается ключевое различие между этими двумя подходами.
Шпаргалка
Используйте наследование для отношений «является» (is-a) и трейты для способностей «имеет» (has-a).
Наследование устанавливает идентичность — дочерний класс является типом родительского класса и может использоваться везде, где ожидается родительский тип.
Трейты добавляют поведение без изменения идентичности класса — класс обладает функциональностью, но не становится этой сущностью.
<?php
// Наследование: Dog ЯВЛЯЕТСЯ Animal
class Animal {
public function breathe(): void {
echo "Breathing";
}
}
class Dog extends Animal {
public function bark(): void {
echo "Woof!";
}
}
// Трейт: Cat ОБЛАДАЕТ возможностью логирования
trait Loggable {
public function log(string $msg): void {
echo "Log: $msg";
}
}
class Cat extends Animal {
use Loggable;
}
| Аспект | Наследование | Трейты |
|---|---|---|
| Отношение | is-a (является) | has-a (обладает способностью) |
| Ограничение | Только один родитель | Разрешено несколько трейтов |
| Проверка типов | Работает с instanceof | Нет типовой идентичности |
| Лучше всего для | Иерархических отношений | Общего поведения для несвязанных классов |
Используйте наследование для иерархических отношений, где классы разделяют общую идентичность. Используйте трейты для общего поведения несвязанных классов.
Попробуйте сами
<?php
// Основной файл - создание и тестирование экземпляра Car
// TODO: Подключите файл Car.php
// Чтение входных данных
$brand = trim(fgets(STDIN));
$doors = intval(trim(fgets(STDIN)));
$location = trim(fgets(STDIN));
// TODO: Создайте экземпляр Car с брендом и количеством дверей
// TODO: Установите местоположение автомобиля
// TODO: Выведите результат describe()
// TODO: Выведите результат getLocation()
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy