Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy. Урок 74 из 87.

State Pattern — это поведенческий шаблон проектирования, который позволяет объекту изменять своё поведение при изменении его внутреннего состояния. Кажется, будто объект изменяет свой класс, но на самом деле он делегирует поведение различным объектам состояний. Представь торговый автомат: его реакция на нажатие кнопки зависит от того, находится ли он в состоянии ожидания, внесены ли деньги или он выдаёт товар.

Вместо использования сложных условных операторов для обработки различных состояний вы инкапсулируете каждое состояние в собственном классе. Шаблон включает Context, который хранит ссылку на текущее состояние, и классы State, определяющие поведение для каждого состояния:

interface State {
    void handle(Context context);
}

class Context {
    private State state;
    
    public void setState(State state) {
        this.state = state;
    }
    
    public void request() {
        state.handle(this);
    }
}

Каждое concrete состояние реализует поведение и может инициировать переходы в другие состояния:

class IdleState implements State {
    public void handle(Context context) {
        System.out.println("Idle: Insert coin to start");
        context.setState(new ActiveState());
    }
}

class ActiveState implements State {
    public void handle(Context context) {
        System.out.println("Active: Processing...");
        context.setState(new IdleState());
    }
}

context делегирует запросы своему current состоянию, и поведение автоматически изменяется по мере изменения состояния:

Context context = new Context();
context.setState(new IdleState());

context.request();  // Idle: Вставьте монету, чтобы начать
context.request();  // Active: Обработка...

Шаблон State идеально подходит, когда поведение объекта сильно зависит от его состояния и должно изменяться во время выполнения. Он устраняет большие блоки условных операторов и упрощает добавление новых состояний: просто создай новый класс, реализующий интерфейс State.

challenge icon

Задание

Легко

Создадим систему рабочего процесса для документов с использованием паттерна «Состояние»! Ты создашь систему, которая моделирует перемещение документа между различными этапами — Draft, Review и Published, — при этом каждое состояние определяет доступные действия и поведение документа при попытке изменить или одобрить его.

Ты организуешь код в четырёх файлах:

  • DocumentState.java: Определи интерфейс DocumentState, который будут реализовывать все состояния. В нём следует объявить два метода: edit(Document doc) для попытки изменить документ и approve(Document doc) для попытки одобрить документ или перевести его на следующий этап.
  • States.java: Создай три конкретных класса состояний, реализующих DocumentState:

    DraftState — при вызове edit выведи Editing draft.... При вызове approve выведи Draft approved. Moving to review. и переведи документ в состояние ReviewState.

    ReviewState — при вызове edit выведи Cannot edit during review. (состояние не изменяется). При вызове approve выведи Review complete. Publishing document. и переведи документ в состояние PublishedState.

    PublishedState — при вызове edit выведи Cannot edit published document.. При вызове approve выведи Document already published.. Ни одно из действий не изменяет состояние.

  • Document.java: Создай класс контекста, который хранит текущее состояние. Твой Document должен содержать:

    Приватное поле, хранящее текущее DocumentState, инициализированное значением DraftState в конструкторе.

    Метод setState(DocumentState state), который изменяет текущее состояние.

    Метод edit(), делегирующий выполнение методу edit текущего состояния.

    Метод approve(), делегирующий выполнение методу approve текущего состояния.

    Метод getStatus(), возвращающий простое имя класса текущего состояния (используй getClass().getSimpleName()).

  • Main.java: Продемонстрируй работу системы состояний! На вход будет подана одна строка: последовательность действий, разделённых запятыми, где каждое действие — это либо edit, либо approve (например: edit,approve,edit,approve).

    Создай Document и выведи его начальный статус в формате Status: [state]. Затем обработай каждое действие по порядку, вызывая соответствующий метод документа. После каждого действия выведи текущее состояние.

На вход будет подана одна строка действий, разделённых запятыми.

Например, при вводе edit,approve,approve результатом будет:

Status: DraftState
Editing draft...
Status: DraftState
Draft approved. Moving to review.
Status: ReviewState
Review complete. Publishing document.
Status: PublishedState

Обрати внимание, как один и тот же вызов approve() даёт разные результаты в зависимости от текущего состояния документа. В этом и заключается суть паттерна «Состояние»! Документ не использует условные операторы для проверки своего состояния; вместо этого он делегирует поведение текущему объекту состояния, который соответствующим образом обрабатывает действие и при необходимости запускает переходы.

Попробуйте сами

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.nextLine();
        
        // TODO: Создать новый Document
        
        // TODO: Вывести начальный статус в формате "Status: [state]"
        
        // TODO: Разделить ввод по запятой, чтобы получить отдельные действия
        
        // TODO: Пройти циклом по каждому действию
        // - Если действие — "edit", вызвать edit() у document
        // - Если действие — "approve", вызвать approve() у document
        // - После каждого действия вывести текущий статус
    }
}
quiz iconПроверьте себя

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

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

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