В чём разница между State и Strategy
С точки зрения структуры классов паттерны State (Состояние) и Strategy (Стратегия) выглядят практически одинаково (оба используют композицию и делегирование через общий интерфей...
🟢 Junior Level
С точки зрения структуры классов паттерны State (Состояние) и Strategy (Стратегия) выглядят практически одинаково (оба используют композицию и делегирование через общий интерфейс). Однако их архитектурное назначение (Intent) принципиально различается:
- Strategy (Стратегия): алгоритм выбирается клиентом извне. Стратегии обычно не знают о существовании друг друга и решают одну задачу разными способами (например, разные методы сортировки или оплаты).
- State (Состояние): поведение объекта меняется изнутри в зависимости от его текущего состояния в рамках жизненного цикла. Состояния часто знают о возможных переходах друг в друга (модель конечного автомата).
Наглядная аналогия
- Strategy: Вы садитесь за руль автомобиля и выбираете режим коробки передач: «Спорт», «Эко» или «Комфорт». Вы сами решаете, какой алгоритм управления двигателем использовать.
- State: Светофор. Он сам последовательно переключается: «Красный» $\rightarrow$ «Желтый» $\rightarrow$ «Зеленый». В зависимости от текущего цвета его реакция на внешние события меняется (при красном машины стоят, при зеленом — едут), и водитель не может произвольно включить зеленый цвет в обход логики автомата.
Быстрый пример кода
// Strategy: клиент сам решает, какую реализацию передать
Order order = new Order();
order.setPaymentStrategy(new CreditCardPayment()); // Внедрение извне
order.pay();
// State: клиент вызывает бизнес-действие, а объект сам переключает состояние
Order order = new Order(); // Внутри: State = NEW
order.processPayment(); // Внутри переключился на PAID
order.ship(); // Внутри переключился на SHIPPED
order.processPayment(); // ❌ Исключение: заказ уже оплачен!
🟡 Middle Level
Разбор паттерна State: Управление жизненным циклом и переходами
Паттерн State реализует концепцию конечного автомата (Finite State Machine, FSM):
// Интерфейс состояния
public interface DocumentState {
void edit(Document doc, String text);
void publish(Document doc);
}
// Контекст
public class Document {
private DocumentState state;
private String content = "";
public Document() {
this.state = new DraftState(); // Начальное состояние
}
public void setState(DocumentState state) {
this.state = state;
}
public void edit(String text) { state.edit(this, text); }
public void publish() { state.publish(this); }
public void setContent(String content) { this.content = content; }
}
// Конкретные состояния
public class DraftState implements DocumentState {
public void edit(Document doc, String text) {
doc.setContent(text);
System.out.println("Черновик отредактирован");
}
public void publish(Document doc) {
System.out.println("Черновик отправлен на модерацию");
doc.setState(new ModerationState()); // Переход в следующее состояние!
}
}
public class PublishedState implements DocumentState {
public void edit(Document doc, String text) {
throw new IllegalStateException("Опубликованный документ нельзя редактировать!");
}
public void publish(Document doc) {
System.out.println("Документ уже опубликован");
}
}
Сравнительная таблица различий
| Критерий | Strategy (Стратегия) | State (Состояние) |
|---|---|---|
| Кто управляет переключением? | Внешний клиент (через конструктор или сеттер). | Сам контекст или классы состояний по правилам переходов. |
| Осведомленность о сосе Neighbors | Стратегии полностью изолированы и ничего не знают друг о друге. | Состояния часто знают о других состояниях, так как инициируют переходы. |
| Мутабельность состояния | Как правило, Stateless (чистый алгоритм). Экземпляр можно сделать Singleton. | Stateful: инкапсулирует статус процесса, правила валидации и переходы. |
| Цель паттерна | Устранить дублирование кода и ветвления при выборе альтернативных алгоритмов. | Устранить спагетти из условных операторов проверки статуса (if (status == PAID)). |
Проблема сильной связанности в State (Tight Coupling) и способы решения
Если конкретное состояние StateA напрямую создает new StateB(), возникает жесткая связность между классами. Способы решения:
- Переходы внутри Контекста: Контекст содержит таблицу допустимых переходов, а состояния лишь возвращают статус операции (
Event/Trigger). - Использование Enum State: Если состояний немного и они не требуют тяжелых зависимостей, реализация через
enumс абстрактными методами является самой чистой в Java.
public enum OrderStatus {
NEW {
@Override
public OrderStatus next() { return PAID; }
},
PAID {
@Override
public OrderStatus next() { return DELIVERED; }
},
DELIVERED {
@Override
public OrderStatus next() { throw new IllegalStateException("Заказ уже завершен"); }
};
public abstract OrderStatus next();
}
🔴 Senior Level
Многопоточность и состояние гонки в паттерне State
В высоконагруженных системах изменение состояния объекта несколькими потоками одновременно — классический источник критических багов (Double Spending, Race Condition при переходах):
- Неатомарная смена состояния:
Если проверка условий и вызов
doc.setState(...)происходят без синхронизации, два параллельных потока могут одновременно перевести один и тот же заказ в статусCANCELLEDиPAID. - Способы обеспечения потокобезопасности:
AtomicReference<State>: подходит для простых линейных автоматов без побочных эффектов с помощьюcompareAndSet().- Оптимистические блокировки в БД (
@Versionв JPA / Hibernate): при конкурентной смене статуса один из потоков получитOptimisticLockExceptionи откатит транзакцию. - Паттерн State Machine с синхронизированной очередью событий.
Enterprise-решения: Spring Statemachine
Когда бизнес-процесс насчитывает десятки состояний (сложные воронки заказов, банковский скоринг, кредитный конвейер), ручная реализация паттерна State превращается в трудноподдерживаемый код. В enterprise-архитектуре используют специализированные фреймворки (например, Spring Statemachine):
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {
transitions
.withExternal()
.source(OrderStatus.NEW).target(OrderStatus.PAID).event(OrderEvent.PAY)
.guard(paymentValidationGuard()) // Проверка условий
.action(sendPaymentReceiptAction()) // Побочный эффект
.and()
.withExternal()
.source(OrderStatus.PAID).target(OrderStatus.SHIPPED).event(OrderEvent.SHIP);
}
}
Преимущества State Machine фреймворков:
- Декларативная таблица переходов.
- Guards (Охранные условия): переход не произойдет, пока условие не вернет
true. - Actions (Действия): автоматический вызов бизнес-логики при входе (
entry), выходе (exit) или переходе. - Интеграция с распределенной персистентностью (сохранение состояния в Redis/Postgres).
🎯 Шпаргалка для интервью
30-секундный ответ (Elevator Pitch)
«Несмотря на визуальную идентичность диаграмм классов, ключевая разница между Strategy и State заключается в назначении и источнике управления. Strategy выбирается внешним клиентом для подмены взаимозаменяемого алгоритма решения задачи (стратегии не знают друг о друге и обычно не хранят состояние). State моделирует конечный автомат (FSM): объект меняет свое поведение самостоятельно изнутри по мере прохождения жизненного цикла, а состояния знают правила допустимых переходов. Для простых сценариев State реализуется через
enum, а для сложных корпоративных процессов используют Spring Statemachine».
3–4 каверзных вопроса с ответами
- Могут ли классы состояний в паттерне State быть Singleton-объектами?
- Ответ: Да, если конкретные классы состояний не хранят собственного изменяемого состояния (stateless), а получают контекст через аргументы методов (
state.handle(Context ctx)). В таком случае экземпляры состояний кэшируются в виде констант илиenum, что снижает нагрузку на сборщик мусора GC.
- Ответ: Да, если конкретные классы состояний не хранят собственного изменяемого состояния (stateless), а получают контекст через аргументы методов (
- Как избежать сильной связанности (tight coupling), когда состояния знают друг о друге при переходе?
- Ответ: Перенести логику переходов в контекст или отдельный класс-медиатор (Transition Manager / Registry). Состояние в этом случае лишь сообщает контексту событие (Event/Trigger), а контекст по таблице переходов определяет следующее состояние.
- Что произойдет, если в многопоточной среде два потока одновременно вызовут смену состояния у одного объекта?
- Ответ: Без синхронизации возникнет состояние гонки (Race Condition), из-за которого объект может перейти в недопустимое состояние или дважды выполнить побочный эффект (например, двойное списание средств). Для защиты используют явные блокировки, атомарные ссылки (
AtomicReference) или оптимистическую блокировку на уровне базы данных (@Versionв JPA).
- Ответ: Без синхронизации возникнет состояние гонки (Race Condition), из-за которого объект может перейти в недопустимое состояние или дважды выполнить побочный эффект (например, двойное списание средств). Для защиты используют явные блокировки, атомарные ссылки (
- Почему для реализации сложных бизнес-процессов предпочитают Spring Statemachine вместо ручного паттерна State?
- Ответ: Ручной паттерн State плохо масштабируется при появлении развилок, иерархических состояний (Sub-states), откатов по таймаутам и требований к аудиту. Spring Statemachine предоставляет готовый декларативный механизм с охранными условиями (Guards), транзакционными действиями (Actions) и сериализацией автомата в базу данных.
Типичные ошибки и Red Flags
- 🚩 Утверждение, что Strategy и State — это одно и то же (фатальная ошибка на собеседовании, демонстрирующая непонимание архитектурного замысла).
- 🚩 Клиентский код напрямую переключает состояния объекта (
order.setState(new DeliveredState())) — это превращает State в хаотичную Strategy и ломает инварианты автомата. - 🚩 Создание тяжелых экземпляров состояний через оператор
newна каждый переход в высоконагруженных циклах вместо переиспользования синглтонов или enum. - 🚩 Игнорирование многопоточной синхронизации при конкурентных переходах состояний.
Связанные темы
- Когда использовать Strategy — разбор паттерна Strategy
- Какие категории паттернов существуют — классификация поведенческих шаблонов GoF
- Что такое паттерны проектирования — введение в паттерны
- Что такое транзакции — атомарность смены состояний
- Hibernate JPA — маппинг состояний и
@Enumerated