Паттерн «Состояние»
Часть раздела Объектно-ориентированное программирование путешествия по 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.
Задание
ЛегкоСоздадим систему рабочего процесса для документов с использованием паттерна «Состояние»! Ты создашь систему, которая моделирует перемещение документа между различными этапами — 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
// - После каждого действия вывести текущий статус
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»Потренируйтесь самостоятельно: Онлайн-компилятор Java