Что это такое
Паттерн State даёт объекту разное поведение в разных состояниях, делая каждое состояние отдельным классом. Объект хранит своё текущее состояние и делегирует ему работу; переход в другое состояние — это просто замена значения.
Он заменяет разрастающийся switch (status) { ... }, повторяющийся в половине методов, одним маленьким классом на каждое состояние.
Пример: жизнь заказа
Заказ сначала новый, затем оплаченный, затем отправленный. Каждый класс состояния отвечает на describe() по-своему и знает, какое состояние идёт следующим, — сам заказ вообще не содержит логики статусов.
Каждое состояние — это класс
advance() навсегда остаётся одной строкой, а три напечатанные строки приходят из трёх разных классов. Добавить состояние «доставлен» — это новый класс плюс один изменённый next(): никакого switch, который нужно искать.
Вывод: программа на TypeScript и захваченный вывод
interface OrderState {
next(): OrderState
describe(): string
}
class NewOrder implements OrderState {
next(): OrderState {
return new Paid()
}
describe(): string {
return "новый - ожидает оплаты"
}
}
class Paid implements OrderState {
next(): OrderState {
return new Shipped()
}
describe(): string {
return "оплачен - упаковываем коробку"
}
}
class Shipped implements OrderState {
next(): OrderState {
return this
}
describe(): string {
return "отправлен - в пути"
}
}
class Order {
state: OrderState = new NewOrder()
advance(): void {
this.state = this.state.next()
}
}
const order = new Order()
console.log(order.state.describe())
order.advance()
console.log(order.state.describe())
order.advance()
console.log(order.state.describe())
Когда его применять
Применяйте, когда поведение объекта действительно различается в зависимости от состояния и у состояний есть настоящие правила переходов: заказы, документы (черновик/на проверке/опубликован), соединения, игровые персонажи.
Не применяйте, когда статус только отображается и никогда не меняет поведение. Строковое объединение вроде "new" | "paid" | "shipped" с одним switch вполне подходит, если состояний меньше трёх и правила простые.
Плюсы и минусы
Плюсы: правила каждого состояния живут в одном маленьком классе; невозможные переходы просто не написаны; новые состояния добавляются расширением, а не изменением.
Минусы: много крошечных классов для простых автоматов; поток всего автомата размазан по файлам — помогает схема в комментарии.