Паттерн «Состояние»
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy. Урок 79 из 91.
Паттерн «Состояние» — это поведенческий шаблон проектирования, который позволяет объекту изменять своё поведение при изменении его внутреннего состояния. Вместо использования сложных условных конструкций для обработки различных состояний вы инкапсулируете каждое состояние в собственном классе и делегируете поведение объекту current состояния.
Представьте торговый автомат. Он ведёт себя по-разному в зависимости от того, ожидает ли он деньги, получил ли оплату или выдаёт товар.
Каждое состояние имеет собственные правила относительно разрешённых действий. Паттерн State моделирует это, создавая class для каждого состояния.
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}
class PendingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ProcessingState());
return "Order moved to processing";
}
public function getStatus(): string {
return "Pending";
}
}
class ProcessingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ShippedState());
return "Order has been shipped";
}
public function getStatus(): string {
return "Processing";
}
}
class ShippedState implements OrderState {
public function proceed(Order $order): string {
return "Order already shipped";
}
public function getStatus(): string {
return "Shipped";
}
}
Класс context хранит ссылку на текущее состояние и Delegate вызовы ему:
<?php
class Order {
private OrderState $state;
public function __construct() {
$this->state = new PendingState();
}
public function setState(OrderState $state): void {
$this->state = $state;
}
public function proceed(): string {
return $this->state->proceed($this);
}
public function getStatus(): string {
return $this->state->getStatus();
}
}
$order = new Order();
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus();
Вывод:
Pending
Order moved to processing
Processing
Order has been shipped
ShippedПаттерн State устраняет разрастающиеся инструкции switch и упрощает добавление новых состояний. Каждый class состояния отвечает за знание того, какое состояние следует следующим, благодаря чему логика остаётся организованной и удобной для сопровождения.
Задание
ЛегкоДавайте создадим систему документооборота с использованием паттерна State. Документы в системе управления контентом проходят через разные этапы: черновик, проверка и публикация; для каждого этапа действуют собственные правила, определяющие, какие действия разрешены и что происходит при попытке перевести документ на следующий этап.
Вы организуете код в четырех файлах:
DocumentState.php: Define интерфейсDocumentState, который устанавливает контракт для всех состояний документа. В нем должно быть два метода:publish(Document $document): stringдля попытки перевести документ на следующий этап иgetStatus(): stringдля возврата имени текущего состояния.States.php: Подключите интерфейс DocumentState и создайте три класса состояний, которые Implement его:DraftState: Когда вызываетсяpublish(), документ переводится в состояниеReviewState, а метод возвращает"Document sent for review". Его статус —"Draft".ReviewState: Когда вызываетсяpublish(), документ переводится в состояниеPublishedState, а метод возвращает"Document published". Его статус —"Under Review".PublishedState: Когда вызываетсяpublish(), метод возвращает"Document is already published", не изменяя состояние. Его статус —"Published".
Document.php: Подключите файл States и создайте классDocument, который выступает в роли контекста. Ваш документ должен:- Accept заголовок в своем конструкторе и начинать в состоянии
DraftState - Have метод
setState(DocumentState $state): voidдля изменения current состояния - Have метод
publish(): string, который делегирует вызов методуpublish()текущего состояния - Have метод
getStatus(): string, который возвращает статус текущего состояния
- Accept заголовок в своем конструкторе и начинать в состоянии
main.php: Подключите файл Document. Вы получите один input: number раз, которое нужно вызватьpublish()для документа.Создайте
Documentс заголовком"My Article". Сначала Print initial статус. Затем вызовитеpublish()указанное number раз, printing результат каждого Call с новой строки. Наконец, Print final статус.Формат вывода:
Status: [initial status] [publish result 1] [publish result 2] ... Status: [final status]
Каждый класс состояния знает, какое состояние идет следующим в workflow, благодаря чему логика переходов остается организованной и ее легко изменять. Добавление нового состояния, такого как "Archived", будет просто означать создание нового класса и обновление соответствующих переходов.
Попробуйте сами
<?php
require_once 'Document.php';
// Читаем ввод - количество раз для вызова publish()
$n = intval(trim(fgets(STDIN)));
// TODO: Создать Document с заголовком "My Article"
// TODO: Вывести начальный статус в формате: "Status: [status]"
// TODO: Вызвать publish() n раз, выводя каждый результат на новой строке
// TODO: Вывести конечный статус в формате: "Status: [status]"
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Повторение — простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыПовторение — иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateМодификаторы доступа подробноГеттеры и сеттерыСокрытие информацииПродвижение конструктора (8.0)Повторение — система учёта студентов10Продвинутые концепции ООП
Композиция и наследованиеВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Репозиторий»2Пространства имён и автозагрузка
Введение в пространства имёнКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerПовторение — структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс и абстрактный классАннотация типов с интерфейсамиПовторение — калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeПовторение — пользовательская коллекция3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищённые свойстваСвойства только для чтения (PHP 8.1)Повторение — менеджер банковского счёта6Полиморфизм
Повторение переопределения методовПолиморфизм через интерфейсыАннотации типов и объединённые типыПозднее статическое связываниеПовторение — обработчик платежей9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования, часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн StrategyПотренируйтесь самостоятельно: Онлайн-компилятор PHP