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);
    }
}

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

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 context = new Context();
context.setState(new IdleState());

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

Паттерн Состояние (State) идеален в тех случаях, когда поведение объекта сильно зависит от его состояния и должно изменяться во время выполнения. Он устраняет громоздкие условные конструкции и упрощает добавление новых состояний — достаточно создать новый класс, реализующий интерфейс State.

challenge icon

Задание

Легко

Давайте создадим систему документооборота, используя паттерн Состояние (State)! Вы создадите систему, которая моделирует перемещение документа по различным этапам — 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() дает разные результаты в зависимости от текущего состояния документа — в этом и заключается суть паттерна Состояние! Документ не использует условные операторы для проверки своего состояния; вместо этого он делегирует поведение объекту текущего состояния, который соответствующим образом обрабатывает действие и инициирует переходы при необходимости.

Шпаргалка

Паттерн Состояние (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);
    }
}

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

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 context = new Context();
context.setState(new IdleState());

context.request();  // Idle: Insert coin to start
context.request();  // Active: Processing...

Паттерн Состояние идеален в тех случаях, когда поведение объекта сильно зависит от его состояния и должно изменяться во время выполнения. Он устраняет большие блоки условных операторов и упрощает добавление новых состояний.

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

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() у документа
        // - Если действие "approve", вызвать approve() у документа
        // - После каждого действия выводить текущий статус
    }
}
quiz iconПроверьте себя

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

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