
В мире разработки программного обеспечения управление состоянием объекта - одна из фундаментальных задач. Когда поведение объекта должно меняться в зависимости от его внутреннего состояния, разработчики часто обращаются к паттерну State. Однако здесь и возникает путаница: его нередко отождествляют с более общей концепцией — State Machine (Конечный автомат), а то и вовсе не видят разницы.
На самом деле, State — это лишь один из способов реализовать конечный автомат в ООП, и он прекрасен в своей простоте. Но что делать, когда логика усложняется, появляются асинхронные переходы, а количество состояний и событий растет? В этот момент простой паттерн State уступает место полноценным State Machine (FSM). А когда и их возможностей становится недостаточно для описания по-настоящему сложных систем с иерархией и параллельными процессами, на сцену выходят Statecharts.
В этой статье пройдем путь от простого к сложному:
Разберем паттерн State как самостоятельную единицу и поймем, где его применять.
Выясним, в какой момент его возможностей становится мало и на помощь приходит State Machine, рассмотрев практичную табличную реализацию.
Наконец, доберемся до Statecharts и их реализации в Spring State Machine, которая позволяет элегантно управлять даже самой запутанной логикой состояний.
Погрузимся в мир управления состояниями — от простого к сложному!
Статья будет полезна Java-разработчикам, архитекторам и всем, кто хочет глубже понять поведенческие паттерны и их практическое применение.
State ≠ State Machine: разбираем поведенческий паттерн, который часто путают
State паттерн ("Состояние") — это поведенческий паттерн проектирования, который позволяет объекту изменять своё поведение в зависимости от внутреннего состояния. Многие описывают этот паттерн в контексте паттерна State Machine ("Конечный автомат", "Машина состояний") и не рассматривают в отрыве от этой концепции. На самом деле State самостоятельный паттерн и часто используется независимо.
Когда использовать State
Паттерн State особенно полезен когда:
Логика поведения объекта зависит от его различных состояний.
Логика содержит множество условных операторов (
if/else,switch), зависящих от текущего состояния или внутренних флагов.Хотим убрать связь между состоянием и поведением.
Есть множество состояний и они могут меняться со временем.
Примеры:
Текстовый редактор с режимами: чтение, редактирование, навигация.
Состояния заказа/платежа.
Жизненный цикл кредита/займа.
Транзакции между счетами.
Активность пользовательского аккаунта.
Пример реализации на Java
Рассмотрим реализацию состояний текстового редактора.

Есть множество вариантов реализации патт��рна State, некоторые из них могут нарушать принцип LSP(Liskov Substitution Principle), но тем не менее они представлены на множестве ресурсов. Рассмотрим вариант без нарушения принципа LSP:
Определяем интерфейс состояния:
public interface EditorState {
void handle(TextEditor context);
}
Конкретные состояния реализуют интерфейс EditorState:
public class ReadingState implements EditorState {
@Override
public void handle(TextEditor context) {
System.out.println("Вы находитесь в режиме чтения. Просматривайте документ.");
System.out.println("Переход в режим редактирования.");
context.setState(new EditingState());
}
}
public class EditingState implements EditorState {
@Override
public void handle(TextEditor context) {
System.out.println("Вы находитесь в режиме редактирования. Вносите изменения в текст.");
System.out.println("Переход в режим навигации.");
context.setState(new NavigatingState());
}
}
public class NavigatingState implements EditorState {
@Override
public void handle(TextEditor context) {
System.out.println("Вы находитесь в режиме навигации. Просматривайте разделы.");
System.out.println("Переход в режим чтения.");
context.setState(new ReadingState());
}
}
Контекст, который хранит текущее состояние:
public class TextEditor {
// Текущее состояние редактора
private EditorState currentState;
public TextEditor() {
this.currentState = new EditingState(); // начальное состояние
}
public void setState(EditorState state) {
this.currentState = state;
}
public void performAction() {
currentState.handle(this);
}
}
Тестирование:
public class Main
public static void main(String[] args) {
TextEditor editor = new TextEditor();
// Цикл переключений состояний
for (int i = 0; i < 3; i++) {
editor.performAction();
System.out.println("----------------------------");
}
}
}
Результат:
Вы находитесь в режиме редактирования. Вносите изменения в текст.
Переход в режим навигации.
----------------------------
Вы находитесь в режиме навигации. Просматривайте разделы.
Переход в режим чтения.
----------------------------
Вы находитесь в режиме чтения. Просматривайте документ.
Переход в режим редактирования.
----------------------------
Преимущества, которые мы получим, когда используем State
Более чистый и структурированный код за счет применения Single Responsibility и Open-Closed принципов ООП.
Уменьшается связанность между компонентами.
Расширяемость - добавление нового состояния требует минимального изменения существующих.
Инкапсуляция логики.
State Machine: когда State уже недостаточно
Паттерн State отлично подходит для управления поведением объекта в зависимости от его состояния. Но что делать, когда логика переходов между состояниями усложняется? Например в наших требованиях появляются:
Множественные события перехода в одно состояние.
Сложные условия перехода, зависящие от внешних факторов.
Асинхронные переходы - например, ожидание ответа от API.
Необходимость мониторинга и/или отката состояний (нужна история состояний).
Динамическая конфигурация состояний - логика состояний должна быть гибкой и изменяемой без перекомпиляции кода.
В таких случаях подходящим решением будет использование - State Machine/Finite State Mashine (FSM - Конечный автомат).
История появления FSM
Идея конечного автомата (FSM) уходит корнями в математику и теорию автоматов 1940–1950-х годов. Она была описана в работах таких учёных, как Алан Тьюринг, Стивен Клини и Майкл Рабин, как модель вычислений.
В 1956 году — Джордж Мили и Эдвард Мур (не путать с Гордоном Муром из Intel) независимо друг от друга формализовали конечные автоматы (Finite State Machines, FSM):
Автомат Мили — выход зависит от состояния и входа.
Автомат Мура — выход зависит только от состояния. Эти модели стали основой для проектирования цифровых схем, компиляторов, сетевых протоколов и теории формальных языков.
FSM стали основой цифровой логики, компиляторов, сетевых протоколов и теории формальных языков.
Стоит отметить: В книге "Design Patterns: Elements of Reusable Object-Oriented Software" ("банды четырёх", GoF) описан паттерн State, но не State Machine/FSM.
Преимущества и недостатки State Machine
Преимущества:
Централизованная логика и наглядная модель переходов между состояниями.
Легко визуализировать (например, с помощью диаграмм состояний). Некоторые реализации FSM позволяют генерировать диаграммы по коду.
Удобно использовать для отладки и логирования.
Поддерживаются сложные переходы, события, действия и условные переходы.
Хорошо масштабируется.
Легко добавлять новые состояния и переходы, не затрагивая существующую логику состояний.
Много готовых реализаций.
Недостатки:
Требует больше кода и архитектурной подготовки.
Может быть избыточна для простых сценариев.
Реализация может быть очень громоздкой.
Если машина состояний слишком большая, её сложно читать и отлаживать.
Интересная реализация State Machine - Табличная (Table-Driven) реализация
В этой реализации вместо отдельных классов для каждого состояния используется таблица (Map, но можно использовать и массив), которая хранит информацию о переходах состояний и связанных с ними действиях.
Сначала определим основы:
enum State {
IDLE, PROCESSING, COMPLETED, ERROR
}
enum Event {
START, SUCCESS, FAIL, RESET
}
interface StateHandler {
void onEnter(State from, State to, Event event);
}
Теперь определим State Machine с таблицей переходов:
public class TableBasedStateMachine {
private State currentState = State.IDLE;
// Таблица переходов: event -> (от какого состояния -> в какое состояние)
private static final Map<Event, Map<State, State>> transitions = Map.of(
Event.START, Map.of(State.IDLE, State.PROCESSING),
Event.SUCCESS, Map.of(State.PROCESSING, State.COMPLETED),
Event.FAIL, Map.of(State.PROCESSING, State.ERROR),
Event.RESET, Map.of(State.COMPLETED, State.IDLE, State.ERROR, State.IDLE));
// Обработчики входа в состояние
private static final Map<State, StateHandler> handlers = Map.of(
State.IDLE, (from, to, event) -> System.out.println("Entered IDLE from " + from + " via " + event),
State.PROCESSING, (from, to, event) -> System.out.println("Started processing..."),
State.COMPLETED, (from, to, event) -> System.out.println("Processing completed successfully."),
State.ERROR, (from, to, event) -> System.out.println("Error occurred during processing."));
public void handle(Event event) {
Map<State, State> stateMap = transitions.getOrDefault(event, Map.of());
State nextState = stateMap.get(currentState);
if (nextState != null && nextState != currentState) {
State oldState = currentState;
currentState = nextState;
// Вызов обработчика входа в новое состояние
StateHandler handler = handlers.getOrDefault(currentState, (f, t, e) -> {
});
handler.onEnter(oldState, currentState, event);
System.out.println("Transition: " + oldState + " -> " + currentState);
} else {
System.out.println("No transition from " + currentState + " on " + event);
}
}
public State getCurrentState() {
return currentState;
}
}Тестирование и запуск:
public class Main {
public static void main(String[] args) {
TableBasedStateMachine sm = new TableBasedStateMachine();
sm.handle(Event.START); // IDLE -> PROCESSING
sm.handle(Event.SUCCESS); // PROCESSING -> COMPLETED
sm.handle(Event.RESET); // COMPLETED -> IDLE
sm.handle(Event.FAIL); // No transition
}
}
Преимущества такого подхода:
Наглядная таблица переходов — легко читать, расширить или загрузить из конфигурационных файлов (YAML/JSON).
Изоляция разработчиков — легко тестируются и не захламляют переходную логику.
В отличие от классического
switch, добавление нового состояния — просто расширение таблицы.
Возможность использовать одну и ту же таблицу переходов, но разные обработчики.
Statechart и Spring State Machine: когда FSM уже недостаточно
Выше мы рассмотрели классические State Machine. Посмотрели на реализацию FSM с использованием таблиц. Но что делать, когда система разрастается и появляются такие требования:
Вложенные (иерархические) состояния: состояние может иметь подсостояния, и переход из родительского состояния влияет на все его дочерние состояния.
Параллельные (ортогональные) состояния: объект может находиться в нескольких независимых состояниях одновременно.
История состояний: Возможность вернуться в последнее активное подсостояние после выхода из родительского состояния.
Входные/выходные действия: действия, которые выполяются при входе или выходе из состояния.
Визуализация сложной логики переходов: логика переходов становится настолько большой, что ее трудно понять, посмотрев код и требуется некоторая визуализация.
Здесь на помощь приходит Statechart — расширение FSM, предложенное Дэвидом Харелом в 1987 году. Он разработал их для моделирования сложных авиационных систем, где плоские FSM приводили к "взрыву состояний" и становились неуправляемыми. Ссылка на статью по Statechart (здесь немного модифицированная статья) - https://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf. Здесь более подробно и с математическими выводами - https://is.ifmo.ru/works/pattern.pdf
Statechart: Эволюция конечных автоматов
Основная идея Statechart - добавление концепции иерархии (вложенных состояний), параллелизма и истории, что позволяет создавать более компактные и наглядные модели сложных систем.
Ключевые особенности Statechart:
Вложенные состояния (Nested States): состояние может содержать другие состояния. Это позволяет группировать связанные состояния и упрощает диаграммы, так как переходы из родительского состояния применяются ко всем его дочерним состояниям.
Параллельные состояния (Concurrent/Orthogonal States): состояние может быть разделено на несколько независимых ортогональных областей, каждая из которых имеет свой собственный конечный автомат. Это моделирует ситуации, когда объект одновременно выполняет несколько независимых функций.
История (History Pseudo-States): позволяет автомату запоминать последнее активное подсостояние в составном состоянии, чтобы при повторном входе в него вернуться именно в это подсостояние.
Entry/Exit Actions: действия, которые выполняются при входе в состояние или выходе из него.
Internal Transitions: переходы, которые обрабатываются внутри состояния без выхода из него.
Statechart является частью UML (Unified Modeling Language), что делает его широко распространенным и понятным инструментом для моделирования поведения систем. Statechart идеально подходит для систем с множеством взаимосвязанных состояний, например, в workflow-движках или IoT-устройствах.
Spring State Machine: реализация Statechart на Java
Spring State Machine (SSM) — это фреймворк, предоставляющий реализацию концепции Statechart для Spring-приложений. Он предлагает декларативный подход к определению состояний и переходов, множество возможностей для гибкой настройки, интеграцию с другими Spring-проектами и многое другое.
Основные возможности Spring State Machine:
Декларативное определение: состояния и переходы определяются с помощью конфигурации (Java-конфигурации или XML).
Вложенные состояния: полная поддержка иерархических состояний.
Параллельные состояния: возможность моделировать параллельные процессы.
Действия (Actions): можно привязывать действия к переходам, входу/выходу из состояний.
Условные переходы (Guards): условия, которые должны быть выполнены для осуществления перехода.
События (Events): механизм для запуска переходов.
Персистентность (Persistence): возможность сохранять и восстанавливать состояние конечного автомата (например, в БД).
Мониторинг: интеграция с Spring Actuator для мониторинга состояния машины.
Визуализация: можно определять состояния и переходы используя диаграммы состояний через eclipse Papyrus framework (к сожалению в 4 версии эту возможность удалили), вот тут описано как это сделать - https://docs.spring.io/spring-statemachine/docs/current/reference/#sm-papyrus.
Давайте рассмотрим пример использования Spring State Machine для сценария "обработки запроса", с более сложной логикой, включая вложенные состояния.
Обработка Запроса с Вложенными Состояниями
Пусть, наше состояние PROCESSING может быть детализировано на подсостояния: INITIALIZING, EXECUTING, FINALIZING. -
/ Перечисления для состояний и событий
enum States {
IDLE,
PROCESSING,
PROCESSING_INITIALIZING,
PROCESSING_EXECUTING,
PROCESSING_FINALIZING,
COMPLETED,
ERROR
}
enum Events {
START,
INITIALIZE_DONE,
EXECUTE_DONE,
FINALIZE_DONE,
SUCCESS,
FAIL,
RESET
}

Сама конфигурация StateMachine с логикой переходов между состояниями, условиями и вложенными состояниями будет выглядеть так:
@Configuration
@EnableStateMachine
public class RequestStateMachineConfig extends StateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(new StateMachineListener()); // Добавим слушателя для логирования
}
@Override
public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
states
.withStates()
.initial(States.IDLE)
.state(States.COMPLETED)
.state(States.ERROR)
.state(States.PROCESSING, processingActions()) // Действия для родительского состояния PROCESSING
.end(States.COMPLETED) // Конечные состояния для ветки
.end(States.ERROR)
.and()
.withStates()
.parent(States.PROCESSING) // Определяем вложенные состояния
.initial(States.PROCESSING_INITIALIZING)
.state(States.PROCESSING_EXECUTING)
.state(States.PROCESSING_FINALIZING);
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
transitions
.withExternal()
.source(States.IDLE).target(States.PROCESSING).event(Events.START)
.action(startProcessingAction())
.and()
.withExternal()
.source(States.PROCESSING_INITIALIZING).target(States.PROCESSING_EXECUTING)
.event(Events.INITIALIZE_DONE)
.action(initializeDoneAction())
.and()
.withExternal()
.source(States.PROCESSING_EXECUTING).target(States.PROCESSING_FINALIZING).event(Events.EXECUTE_DONE)
.action(executeDoneAction())
.and()
.withExternal()
.source(States.PROCESSING_FINALIZING).target(States.COMPLETED).event(Events.FINALIZE_DONE)
.action(finalizeDoneAction())
.and()
.withExternal()
.source(States.PROCESSING).target(States.ERROR).event(Events.FAIL) // Переход из родительского состояния
.action(failAction())
.and()
.withExternal()
.source(States.COMPLETED).target(States.IDLE).event(Events.RESET)
.action(resetAction())
.and()
.withExternal()
.source(States.ERROR).target(States.IDLE).event(Events.RESET)
.action(resetAction());
}
// Действия (Actions)
public Action<States, Events> startProcessingAction() {
return context -> System.out.println("Entering PROCESSING state from IDLE.");
}
public Action<States, Events> initializeDoneAction() {
return context -> System.out.println("Processing: Initializing done. Moving to EXECUTING.");
}
// ....
// Логирование состояний
static class LoggingStateStateMachineListener
extends StateMachineListenerAdapter<States, Events> {
@Override
public void stateChanged(State<States, Events> from, State<States, Events> to) {
if (from != null) {
System.out.println("Transition: " + from.getId() + " -> " + to.getId());
} else {
System.out.println("Initial state: " + to.getId());
}
}
}
}
Проверить результаты работы можно через запуск:
@SpringBootApplication
public class SpringFsmDemoApplication implements CommandLineRunner {
@Autowired
private StateMachine<States, Events> stateMachine;
public static void main(String[] args) {
SpringApplication.run(SpringFsmDemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("Current state: " + stateMachine.getState().getId());
stateMachine.sendEvent(Events.START);
System.out.println("Current state: " + stateMachine.getState().getId()); // Должно быть PROCESSING_INITIALIZING
stateMachine.sendEvent(Events.INITIALIZE_DONE);
System.out.println("Current state: " + stateMachine.getState().getId()); // Должно быть PROCESSING_EXECUTING
stateMachine.sendEvent(Events.EXECUTE_DONE);
System.out.println("Current state: " + stateMachine.getState().getId()); // Должно быть PROCESSING_FINALIZING
stateMachine.sendEvent(Events.FINALIZE_DONE);
System.out.println("Current state: " + stateMachine.getState().getId()); // Должно быть COMPLETED
stateMachine.sendEvent(Events.RESET);
System.out.println("Current state: " + stateMachine.getState().getId()); // Должно быть IDLE
}
}
Полный код проекта доступен по ссылке.
Итоги
Выбор инструмента для управления состояниями напрямую зависит от масштаба задачи и сложности.
Паттерн State — отличный выбор, когда нужно инкапсулировать логику, зависящую от состояния, внутри объекта, избавляясь от громоздких условных операторов. Он прост, элегантен и идеально подходит для сценариев, где переходы между состояниями несложны и управляются самим объектом. Это отличная реализация принципов SOLID для локальных задач.
State Machine (FSM) — это следующий шаг, когда логика переходов становится явной и централизованной. FSM необходима, когда есть четко определенные события (events), вызывающие переходы.
Statecharts (и их реализация, например, Spring State Machine) — это решение для промышленных систем. Стоит использовать когда нужны:
Иерархические (вложенные) состояния для группировки логики.
Параллельные (ортогональные) состояния для моделирования независимых процессов.
История состояний, условные переходы (guards) и действия (actions).
Таблица с сравнением характеристик:
Характеристика | State | State Machine (FSM) | Statechart / Spring SM |
|---|---|---|---|
Простота | ✅ Простая | ⚠️ Средняя сложность | ❗ Сложная |
Поддержка событий | ❌ Нет | ✅ Да | ✅ Да |
Вложенные состояния | ❌ Нет | ❌ Нет | ✅ Да |
Поддержка истории | ❌ Нет | ❌ Нет | ✅ Да |
Параллельные состояния | ❌ Нет | ❌ Нет | ✅ Да |
Визуализация | ❌ Ограничена | ✅ Возможна | ✅ Встроенная поддержка |
Spring State Machine предоставляет весь этот арсенал "из коробки", интегрируя его в экосистему Spring и добавляя персистентность, мониторинг и возможность визуализации (не во всех версиях).
Проще говоря, начинайте с State. Если переходы становятся сложными и требуют явного управления — переходите к State Machine. А если ваша система напоминает сложный производственный процесс - смело берите на вооружение Statecharts. Правильный выбор не только сделает ваш код чище, но и сэкономит массу времени на отладку и поддержку в будущем.
Управление состоянием — это не просто техническая задача, а философия проектирования. Правильно выбранный подход превращает хаотичную логику в четкую, тестируемую и документируемую систему. Помните: хорошая архитектура не усложняет, а упрощает. Начинайте просто, масштабируйтесь осознанно, и ваши состояния всегда будут под контролем.
Спасибо за внимание!
Трансформируйтесь от хаоса к гармонии.
Если интересно, подписывайтесь на мой канал, там больше тем, которые не входят в формат Хабра.
