Абстрактные методы в трейтах
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 54 из 91.
Трейты могут предоставлять готовые к использованию методы, но иногда вам нужно, чтобы класс, использующий трейт, реализовал определенную функциональность. Именно здесь становятся полезными абстрактные методы в трейтах — они определяют контракт, который должен выполнить использующий их класс.
Когда трейт объявляет абстрактный метод, любой класс, использующий этот трейт, должен его реализовать:
<?php
trait Notifiable {
abstract public function getEmail(): string;
public function sendNotification(string $message): void {
echo "Sending to " . $this->getEmail() . ": $message";
}
}
class User {
use Notifiable;
public function __construct(private string $email) {}
public function getEmail(): string {
return $this->email;
}
}
$user = new User("john@example.com");
$user->sendNotification("Welcome!");
Вывод:
Sending to john@example.com: Welcome!Трейт предоставляет метод sendNotification(), но полагается на то, что getEmail() будет реализован классом. Это создает мощный паттерн, в котором трейт обрабатывает общую логику, делегируя специфические детали реализующему классу.
Если класс использует трейт без реализации абстрактного метода, PHP выдает фатальную ошибку. Это гарантирует, что трейты могут безопасно полагаться на существование определенных методов, делая ваш код более предсказуемым и удобным в сопровождении.
Задание
ЛегкоДавайте создадим систему ценообразования, в которой различные типы продуктов рассчитывают свои окончательные цены по-разному, но все они используют общую логику скидок через трейт с абстрактным методом.
Вы организуете свой код в трех файлах:
Discountable.php— Создайте трейт с именемDiscountable, который обеспечивает функциональность скидок. Трейт должен содержать:- Абстрактный метод
getBasePrice(): float, который должны реализовать классы, использующие этот трейт - Метод
applyDiscount(int $percent), который рассчитывает и возвращает цену после применения процента скидки к базовой цене - Метод
getFinalPrice(int $percent), который возвращает отформатированную строку:"Final price: $[discounted_price]"с 2 знаками после запятой
- Абстрактный метод
Product.php— Создайте классProduct, который использует трейтDiscountable. Подключите файл трейта. Используйте продвижение свойств в конструкторе (constructor promotion) для определения приватного свойства$name(string) и приватного свойства$price(float). Реализуйте обязательный методgetBasePrice(), чтобы он возвращал цену продукта.main.php— Подключите файл Product. Вы получите три входных значения: название продукта, цену и процент скидки. Создайте экземплярProductи выведите две строки:- Базовую цену, отформатированную как
"Base: $[price]"с 2 знаками после запятой - Результат вызова
getFinalPrice()с процентом скидки
- Базовую цену, отформатированную как
Этот паттерн эффективен, так как трейт Discountable может использоваться любым классом — продуктами, услугами, подписками — до тех пор, пока они реализуют getBasePrice(). Трейт предоставляет общую логику скидок, в то время как каждый класс сам определяет, откуда берется его базовая цена.
Шпаргалка
Трейты могут объявлять абстрактные методы, которые должны быть реализованы любым классом, использующим этот трейт:
<?php
trait Notifiable {
abstract public function getEmail(): string;
public function sendNotification(string $message): void {
echo "Sending to " . $this->getEmail() . ": $message";
}
}
class User {
use Notifiable;
public function __construct(private string $email) {}
public function getEmail(): string {
return $this->email;
}
}
Трейт предоставляет готовые к использованию методы, требуя при этом от класса реализации конкретной функциональности. Если класс использует трейт без реализации абстрактного метода, PHP выдает фатальную ошибку.
Этот паттерн позволяет трейтам обрабатывать общую логику, делегируя специфические детали реализующему классу.
Попробуйте сами
<?php
require_once 'Product.php';
// Чтение входных данных
$name = trim(fgets(STDIN));
$price = floatval(trim(fgets(STDIN)));
$discountPercent = intval(trim(fgets(STDIN)));
// TODO: Создать экземпляр Product с именем и ценой
// TODO: Вывести базовую цену, отформатированную как "Base: $[price]" с 2 знаками после запятой
// TODO: Вывести результат вызова getFinalPrice() с процентом скидки
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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