Паттерн «Состояние»
Часть раздела Объектно-ориентированное программирование путешествия по 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.
Задание
ЛегкоДавайте создадим систему документооборота, используя паттерн Состояние (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() у документа
// - После каждого действия выводить текущий статус
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»