Паттерн Состояние
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 79 из 91.
Паттерн Состояние (State Pattern) — это поведенческий паттерн проектирования, который позволяет объекту изменять свое поведение при изменении его внутреннего состояния. Вместо использования сложных условных операторов для обработки различных состояний, вы инкапсулируете каждое состояние в отдельный класс и делегируете поведение текущему объекту состояния.
Представьте себе торговый автомат. Он ведет себя по-разному в зависимости от того, ожидает ли он оплаты, получил ли деньги или выдает товар.
У каждого состояния есть свои правила относительно того, какие действия разрешены. Паттерн «Состояние» (State Pattern) моделирует это, создавая класс для каждого состояния.
<?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";
}
}
Класс контекста хранит ссылку на текущее состояние и делегирует ему вызовы:
<?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Паттерн Состояние устраняет разрастающиеся операторы switch и делает добавление новых состояний простым. Каждый класс состояния отвечает за знание того, какое состояние следует за ним, сохраняя логику организованной и удобной для поддержки.
Задание
ЛегкоДавайте создадим систему документооборота, используя паттерн «Состояние» (State Pattern). Документы в системе управления контентом проходят через различные этапы — черновик, проверка и публикация — и на каждом этапе действуют свои правила относительно разрешенных действий и того, что происходит при попытке продвинуть документ дальше.
Вы распределите свой код по четырем файлам:
DocumentState.php— Определите интерфейсDocumentState, который устанавливает контракт для всех состояний документа. Он должен иметь два метода:publish(Document $document): stringдля попытки продвижения документа иgetStatus(): stringдля возврата названия текущего состояния.States.php— Подключите интерфейс DocumentState и создайте три класса состояний, реализующих его: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, который выступает в роли контекста. Ваш документ должен:- Принимать заголовок в конструкторе и начинаться в состоянии
DraftState - Иметь метод
setState(DocumentState $state): voidдля изменения текущего состояния - Иметь метод
publish(): string, который делегирует выполнение методуpublish()текущего состояния - Иметь метод
getStatus(): string, который возвращает статус текущего состояния
- Принимать заголовок в конструкторе и начинаться в состоянии
main.php— Подключите файл Document. Вы получите на вход одно число: количество вызововpublish()для документа.Создайте
Documentс заголовком"My Article". Сначала выведите начальный статус. Затем вызовитеpublish()указанное количество раз, выводя результат каждого вызова на новой строке. В конце выведите итоговый статус.Формат вывода:
Status: [initial status] [publish result 1] [publish result 2] ... Status: [final status]
Каждый класс состояния знает, какое состояние следует за ним в рабочем процессе, что делает логику переходов организованной и легкой для изменения. Добавление нового состояния, например "Archived", будет означать просто создание нового класса и обновление соответствующих переходов.
Шпаргалка
Паттерн Состояние (State Pattern) — это поведенческий паттерн проектирования, который позволяет объекту изменять свое поведение при изменении его внутреннего состояния путем инкапсуляции каждого состояния в отдельный класс.
Определите интерфейс состояния, который будут реализовывать все классы состояний:
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}Создайте конкретные классы состояний, реализующие интерфейс:
<?php
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";
}
}Создайте класс контекста, который хранит ссылку на текущее состояние и делегирует ему вызовы:
<?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"; // Ожидание (Pending)
echo $order->proceed() . "\n"; // Заказ переведен в обработку (Order moved to processing)
echo $order->getStatus() . "\n"; // Обработка (Processing)
echo $order->proceed() . "\n"; // Заказ был отправлен (Order has been shipped)
echo $order->getStatus(); // Отправлен (Shipped)Паттерн Состояние избавляет от сложных операторов switch и делает добавление новых состояний простым процессом. Каждый класс состояния отвечает за знание того, какое состояние следует за ним.
Попробуйте сами
<?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Подробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данных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