Что это
Паттерн Состояние (State) задаёт объекту разное поведение в разных состояниях, делая каждое состояние отдельным классом. Объект хранит своё текущее состояние и делегирует ему работу; переход в другое состояние означает подмену этого объекта.
Он заменяет разрастающийся match ($status) { ... }, повторяющийся в половине методов, одним небольшим классом на каждое состояние.
Пример: жизнь заказа
Заказ сначала новый, потом оплаченный, потом отправленный. Каждый класс состояния отвечает на describe() по-своему и знает, какое состояние идёт следующим, — сам заказ не содержит никакой логики статусов.
Каждое состояние — класс
advance() навсегда остаётся одной строкой. Добавить состояние «доставлен» — это новый класс плюс одно изменение в next(): никаких match, которые надо выискивать. (Состояния носят суффикс State, потому что new — ключевое слово и не может быть именем класса.)
Вывод: PHP-скрипт
<?php
interface State {
public function next(): State;
public function describe(): string;
}
class NewState implements State {
public function next(): State { return new PaidState(); }
public function describe(): string { return "новый - ожидает оплаты"; }
}
class PaidState implements State {
public function next(): State { return new ShippedState(); }
public function describe(): string { return "оплачен - упаковываем коробку"; }
}
class ShippedState implements State {
public function next(): State { return $this; }
public function describe(): string { return "отправлен - в пути"; }
}
class Order {
public function __construct(private State $state) {}
public function advance(): void {
$this->state = $this->state->next();
}
public function describe(): string {
return $this->state->describe();
}
}
$order = new Order(new NewState());
echo $order->describe(), "\n";
$order->advance();
echo $order->describe(), "\n";
$order->advance();
echo $order->describe(), "\n";
Когда применять
Применяйте, когда поведение объекта действительно различается по состоянию и у состояний есть реальные правила переходов: заказы, документы (черновик/на проверке/опубликован), соединения, игровые персонажи.
Не применяйте, когда статус только отображается и никогда не меняет поведение. Для менее чем трёх состояний с простыми правилами достаточно строкового свойства и одного match.
Плюсы и минусы
Плюсы: правила каждого состояния живут в одном небольшом классе; невозможные переходы просто не написаны; новые состояния добавляются расширением, а не правкой существующего кода.
Минусы: множество крохотных классов для простых автоматов; поток работы всего автомата размазан по файлам — помогает схема в комментарии.