Что это такое
Паттерн State («Состояние») даёт объекту разное поведение в разных состояниях за счёт того, что каждое состояние становится отдельным типом. Объект хранит своё текущее состояние и передаёт работу ему; переход в другое состояние — это просто замена значения.
Он заменяет разрастающийся switch status { ... }, повторённый в половине методов, одним маленьким типом на каждое состояние.
Пример: жизнь заказа
Заказ сначала новый, потом оплаченный, потом отправленный. Каждый тип состояния отвечает на Describe() по-своему и знает, какое состояние идёт следующим, — сам заказ вообще не содержит логики статусов.
Каждое состояние — это тип
Advance() навсегда останется в одну строку. Добавить состояние «доставлен» — это новый тип плюс один изменённый Next(): не нужно искать по коду ни одного switch.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
type State interface {
Next() State
Describe() string
}
type New struct{}
func (New) Next() State { return Paid{} }
func (New) Describe() string { return "новый — ждём оплату" }
type Paid struct{}
func (Paid) Next() State { return Shipped{} }
func (Paid) Describe() string { return "оплачен — упаковываем коробку" }
type Shipped struct{}
func (Shipped) Next() State { return Shipped{} }
func (Shipped) Describe() string { return "отправлен — в пути" }
type Order struct {
state State
}
func (o *Order) Advance() {
o.state = o.state.Next()
}
func main() {
o := &Order{state: New{}}
fmt.Println(o.state.Describe())
o.Advance()
fmt.Println(o.state.Describe())
o.Advance()
fmt.Println(o.state.Describe())
}
Когда его использовать
Используйте, когда поведение объекта действительно различается в зависимости от состояния и у состояний есть настоящие правила переходов: заказы, документы (черновик/на проверке/опубликован), соединения, персонажи в играх.
Не используйте, когда статус только показывается и никогда не меняет поведение. Если состояний меньше трёх и правила простые, поля-строки и одного switch вполне достаточно.
Плюсы и минусы
Плюсы: правила каждого состояния живут в одном маленьком типе; невозможные переходы просто не написаны; новые состояния добавляются, а не изменяют существующий код.
Минусы: много крошечных типов для простых автоматов; ход работы всего автомата разбросан по файлам — здесь помогает схема в комментарии.