State Pattern — Состояние
Конечный автомат (FSM): объект меняет поведение в зависимости от внутреннего состояния. Метод = дуга в графе переходов.
Проблема
Объект имеет несколько состояний (заказ такси: «ищет», «едет», «отменён»; медиаплеер: idle/playing/paused). Из каждого состояния разрешены только определённые переходы. Хочется без if (state === 'X') { ... } else if (...) — паттерн инкапсулирует каждое состояние в отдельный класс.
Где используется
- Игровые персонажи (idle, walking, attacking, dead)
- Трафик-светофоры, торговые автоматы (FSM)
- Медиаплеер (playing, paused, stopped, loading)
- Lifecycle компонентов (mounting, mounted, updating, unmounting)
- TCP-соединения (closed, listen, established, closing)
- Promise (pending → fulfilled/rejected)
Решение
- Каждое состояние — отдельный класс/объект с методами для разрешённых переходов.
- Контекст хранит ссылку на текущий state-объект.
- Вызов метода контекста делегируется текущему state, который меняет state контекста.
Реализации
Заказ такси
class TaxiOrder {
constructor { this.state = new SearchingState(this); }
search { this.state.search; }
cancel { this.state.cancel(); }
start { this.state.start(); }
}
class SearchingState {
constructor(ctx) { this.ctx = ctx; }
search { console.log('already searching'); }
cancel { this.ctx.state = new CancelledState(this.ctx); }
start { this.ctx.state = new MovingState(this.ctx); }
}
class MovingState {
constructor(ctx) { this.ctx = ctx; }
search { throw new Error('cant search while moving'); }
cancel { this.ctx.state = new CancelledState(this.ctx); }
start { throw new Error('already moving'); }
}
class CancelledState {
constructor(ctx) { this.ctx = ctx; }
search { throw new Error('finished'); }
cancel { console.log('already cancelled'); }
start { throw new Error('finished'); }
}
Media Player
class IdleState {
constructor(player) { this.player = player; }
play { this.player.setState(this.player.playingState); }
pause { /* nothing */ }
stop { /* nothing */ }
}
class PlayingState {
constructor(player) { this.player = player; }
play { /* already playing */ }
pause { this.player.setState(this.player.pausedState); }
stop { this.player.setState(this.player.idleState); }
}
class PausedState {
constructor(player) { this.player = player; }
play { this.player.setState(this.player.playingState); }
pause { /* already paused */ }
stop { this.player.setState(this.player.idleState); }
}
class MediaPlayer {
constructor {
this.idleState = new IdleState(this);
this.playingState = new PlayingState(this);
this.pausedState = new PausedState(this);
this.currentState = this.idleState;
}
setState(state) { this.currentState = state; }
play { this.currentState.play(); }
pause { this.currentState.pause(); }
stop { this.currentState.stop(); }
}
Упрощённо через объекты (без классов)
const trafficLight = {
state: 'red',
states: {
red: { next: 'green', action: => console.log('Стоп!') },
green: { next: 'yellow', action: => console.log('Едем!') },
yellow: { next: 'red', action: => console.log('Внимание!') }
},
transition {
const cur = this.states[this.state];
cur.action;
this.state = cur.next();
}
};
Где используется в JS-экосистеме
- XState — библиотека для FSM в JS/React
- Redux Toolkit / RxJS — машинки состояний
- Promise — внутренне FSM (pending → fulfilled/rejected)
- HTTP Status в fetch (idle → loading → success/error)
- WebSocket states — CONNECTING, OPEN, CLOSING, CLOSED
- Game state — menu, playing, paused, gameover
Подводные камни
- State vs Strategy: Strategy — выбор алгоритма извне; State — переключение изнутри объекта.
- Расширение через Observable State — события при переходе для side-effects.
- Слишком много состояний = сложно поддерживать. Иногда проще plain object с действиями.
- V8 hidden class concern: смена state-объекта меняет shape — может замедлять hot path.
- State Pattern для двух состояний — для boolean флагов избыточен. Применяй когда состояний 3+.
- Состояния знают о конкретных классах контекста — лучше работать через интерфейс
setState.
Главные тезисы автора
- «State — это целая парадигма программирования. За ней FSM».
- «Внутри композиция ссылки на одно из состояний, наружу торчат методы».
- «Метод инициирует переход из состояния в состояние — это дуга в графе».
- Слово 'экшен' — сигнал использовать State.
- «Не из всех состояний переход во все» — граф ограничивает переходы.
- Observable State = State + Observer — события на каждом переходе.
🎓 Источники
- 🎓 GoF Patterns Обзор всех паттернов · 2025-04-29
- State как FSM
- Композиция ссылки на текущее состояние
- Слово 'экшен' = сигнал использовать State
- Observable State = State + Observer
- 🎓 Анализ паттерна State с точки зрения оптимизации V8 · 2025-12-08
- V8 hidden class при смене state
- Влияние на производительность
- refactoring.guru — State