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 — события на каждом переходе.

🎓 Источники

См. также