
В мире разработки программного обеспечения управление состоянием объекта - одна из фундаментальных задач. Когда поведение объекта должно меняться в зависимости от его внутреннего состояния, разработчики часто обращаются к паттерну 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. Правильный выбор не только сделает ваш код чище, но и сэкономит массу времени на отладку и поддержку в будущем.
Управление состоянием — это не просто техническая задача, а философия проектирования. Правильно выбранный подход превращает хаотичную логику в четкую, тестируемую и документируемую систему. Помните: хорошая архитектура не усложняет, а упрощает. Начинайте просто, масштабируйтесь осознанно, и ваши состояния всегда будут под контролем.
Спасибо за внимание!
Трансформируйтесь от хаоса к гармонии.
Если интересно, подписывайтесь на мой канал, там больше тем, которые не входят в формат Хабра.
Комментарии (4)
VladimirFarshatov
30.07.2025 10:11Так и не понял: FSM, Statecharts реализую автомат Мура или Милли?
И второе. Конечный автомат имеет "поток входных событий" (может быть асинхронен), "слушатель потока" тот код, который принимает входной поток событий (например обработчик прерывания) и он в общем случае, может быть отделен от кода КА, например даже аппаратно (привет ООП). Далее есть "текущее состояние" и таблица переходов (для табличного КА) и обработчики состояний, которые также могут изменять состояние КА (тот самый switch для простых решений). Обработчик состояния может как повторять действия текущего состояния (непрерывность, например подачи напряжения на мотор) так и исполнять его разово (исполнитель в переходе из состояние1 в состояние2). Не этим ли, как раз отличаются Милли от Мура? )
Далее, опущено рассмотрение конечных автоматов с памятью, позволяющих не только откат на какое-то предыдущее состояние как в статье, но и отложенные действия для запомненных состояний (разбор контекстно-зависимых грамматик к примеру).
Statecharts - кмк, всего лишь "вложение" нескольких КА один в другой, возможно с единым "слушателем входного потока".
И да, без наличия входного потока событий, как такого конечного автомата не бывает, ибо "нет смысла". Но .. применить можно, как и микроскопом забивать гвозди. )
Собственно тут излагал когда-то теорию КА для детишек под Ардуино с примерами: https://community.alexgyver.ru/threads/programmirovanie-konechnyx-avtomatov-bez-delay.2657/
avkazakov Автор
30.07.2025 10:11Спасибо за комментарий!
Так и не понял: FSM, Statecharts реализую автомат Мура или Милли?
Хороший вопрос! Не силен в теории автоматов, могу предположить что Мили, так как результирующий переход зависит от входящих данных и состояния. Но, могу быть не прав.
Далее, опущено рассмотрение конечных автоматов с памятью, позволяющих не только откат на какое-то предыдущее состояние как в статье, но и отложенные действия для запомненных состояний (разбор контекстно-зависимых грамматик к примеру).
Хорошее замечание. Настолько глубоко не использовал FSM, нужно разобраться в вопросе.
Statecharts - кмк, всего лишь "вложение" нескольких КА один в другой, возможно с единым "слушателем входного потока".
Да, это просто расширение.
ahdenchik
30.07.2025 10:11Не силен в теории автоматов, могу предположить что Мили, так как результирующий переход зависит от входящих данных и состояния. Но, могу быть не прав.
Индустрия давно отказалась от деления конечных автоматов на Мили и Мура - этим теперь только студентов мучают. Делайте каждый переход таким как удобно именно в этом месте и будет норм
ahdenchik
Эта статья - нейросетевой мусор