Menu
Coddy logo textTech

Паттерн «Состояние»

Часть раздела Объектно-ориентированное программирование путешествия по 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 состояния отвечает за знание того, какое состояние следует следующим, благодаря чему логика остаётся организованной и удобной для сопровождения.

challenge icon

Задание

Легко

Давайте создадим систему документооборота с использованием паттерна 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, который возвращает статус текущего состояния
  • 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]"

?>
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор PHP