Паттерны State (Состояние) и State Machine (Конечный автомат), теория
В паттерне Strategy в зависимости от поданного в класс/функцию параметра выполнялись разные варианты действий. По сути это задание «режима» или стратегии работы программы, когда в одном режиме она выполняет одно действие, а в другом — другое.
Паттерн State — тоже про внутренние «режимы работы» программы. Но в отличие от Стратегии, паттерн State фокусируется прежде всего на состояниях системы и переходах между ними, нежели на выполнении конкретных алгоритмов. По сути основной алгоритм работы в State и есть переключение состояния системы из одного в другое по определённым правилам.
В самом простом случае система может состоять из двух состояний: выключено → включено. Чтобы перейти из одного состояния в другое вызывается определённое действие (action). Например, человек «спит» → вызывается action «пробудиться» → человек «бодрствует».
Откуда взялся такой подход, и чем он может быть полезен. Вообще, когда создаётся некая система, обычно естественно думать прежде всего о том, что эта система будет делать, какие выполнять действия, что она поможет достичь.
Такой подход хорошо работает на прямолинейных сценариях с несложной логикой. В случае системы со сложным поведением, если её спроектировать исходя из действий, то тогда в программе начнут появляться «режимы», «флаги» isSomething… и хитросплетения условий с комбинациями значений этих флагов.
Возьмём для примера систему, способную пребывать в трёх состояниях: stopped, playing и paused (простейший плеер). Помимо трёх состояний в системе можно выполнить три действия: play, pause и stop. Кроме того, явно обозначены правила перехода между состояниями с помощью действий:
- состояние
stopped→ действиеplay→ состояниеplaying - состояние
playing→ действиеpause→ состояниеpaused - состояние
playing→ действиеstop→ состояниеstopped - состояние
paused→ действиеplay→ состояниеplaying - состояние
paused→ действиеstop→ состояниеstopped
Вот как будет выглядеть пример на if-ах:
let playerState = "stopped";
function handlePlayerAction(action) { if (playerState === "stopped") { if (action === "play") { console.log("Запуск воспроизведения"); playerState = "playing"; } } else if (playerState === "playing") { if (action === "pause") { console.log("Пауза"); playerState = "paused"; } else if (action === "stop") { console.log("Остановка"); playerState = "stopped"; } } else if (playerState === "paused") { if (action === "play") { console.log("Продолжение воспроизведения"); playerState = "playing"; } else if (action === "stop") { console.log("Остановка с паузы"); playerState = "stopped"; } }}Если подобных ветвлений и состояний появляется более 8-10, то тогда становится сложно держать в голове ментальную модель, код на флагах и if-ах превращается в плохо структурированную кашу, в которой легко допустить ошибку или логическую дыру.
В этом случае как раз и приходит на помощь проектирование системы исходя из её состояний и переходов между ними, а не из выполняемых функций. В каждом состоянии (поле объекта) есть функции-действия, которые возвращают имя нового состояния:
const playerStates = { stopped: { play() { return "playing"; }, pause: null, stop: null, }, playing: { pause() { return "paused"; }, stop() { return "stopped"; }, }, paused: { play() { return "playing"; }, stop() { return "stopped"; }, },};
let currentState = "stopped";
function handlePlayerAction(action) { const stateHandler = playerStates[currentState][action];
if (!stateHandler) { console.log("Действие невозможно в текущем состоянии"); return; }
currentState = stateHandler();}
// Начальное состояние: stoppedhandlePlayerAction("play"); // Запуск воспроизведения → playinghandlePlayerAction("pause"); // Пауза → pausedhandlePlayerAction("play"); // Продолжение → playinghandlePlayerAction("stop"); // Остановка → stoppedПолучившаяся система называется State Machine (конечный автомат), то есть система с конечным числом состояний и чёткими правилами перехода между ними.