Menu
Coddy logo textTech

Трейты и наследование

Часть раздела Объектно-ориентированное программирование путешествия по 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Нет идентификации типа
Лучше всего подходит дляИерархические отношенияОбщее поведение для несвязанных классов

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

challenge icon

Задание

Легко

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

Вы организуете свой код в четырех файлах:

  • Trackable.php — Создайте трейт с названием Trackable, который обеспечивает возможность GPS-отслеживания. Трейт должен иметь:
    • Приватное свойство $location (string)
    • Метод setLocation(string $location), который сохраняет текущее местоположение
    • Метод getLocation(), который возвращает "Location: " . $this->location
    Этот трейт представляет собой возможность «имеет» (has-a) — любое транспортное средство может иметь отслеживание, но наличие отслеживания не делает объект транспортным средством.
  • 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()
?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование