State (Состояние)
Проблема
Объект ведёт себя по-разному в зависимости от текущего состояния. Без паттерна State код превращается в гигантский switch с дублированием проверок. State выносит поведение каждого состояния в отдельный тип.
Диаграмма
+------------------+ +------------------+
| Order |------>| State | <-- interface
+------------------+ +------------------+
| state State | | Process() |
| Process() | | Cancel() |
| Cancel() | | Ship() |
| Ship() | | Deliver() |
+------------------+ +------------------+
^
+--------------+-----------+
| | | |
Pending Processing Shipped Delivered
State State State State
Реализация: Заказ
package order
import "fmt"
// State defines behavior for each order state.
type State interface {
Process(o *Order) error
Cancel(o *Order) error
Ship(o *Order) error
Deliver(o *Order) error
String() string
}
// Order is the context that delegates to its current State.
type Order struct {
ID string
state State
Items []string
Total float64
}
// NewOrder creates an order in Pending state.
func NewOrder(id string, items []string, total float64) *Order {
o := &Order{
ID: id,
Items: items,
Total: total,
}
o.state = &PendingState{}
return o
}
// SetState transitions to a new state.
func (o *Order) SetState(s State) {
fmt.Printf("Order %s: %s -> %s\n", o.ID, o.state, s)
o.state = s
}
// Delegate to current state
func (o *Order) Process() error { return o.state.Process(o) }
func (o *Order) Cancel() error { return o.state.Cancel(o) }
func (o *Order) Ship() error { return o.state.Ship(o) }
func (o *Order) Deliver() error { return o.state.Deliver(o) }
func (o *Order) Status() string { return o.state.String() }
Конкретные состояния
// --- Pending State ---
type PendingState struct{}
func (s *PendingState) String() string { return "pending" }
func (s *PendingState) Process(o *Order) error {
// Validate payment, reserve stock, etc.
o.SetState(&ProcessingState{})
return nil
}
func (s *PendingState) Cancel(o *Order) error {
o.SetState(&CancelledState{})
return nil
}
func (s *PendingState) Ship(o *Order) error {
return fmt.Errorf("cannot ship: order is still pending")
}
func (s *PendingState) Deliver(o *Order) error {
return fmt.Errorf("cannot deliver: order is still pending")
}
// --- Processing State ---
type ProcessingState struct{}
func (s *ProcessingState) String() string { return "processing" }
func (s *ProcessingState) Process(o *Order) error {
return fmt.Errorf("order is already being processed")
}
func (s *ProcessingState) Cancel(o *Order) error {
// Refund payment, release stock
o.SetState(&CancelledState{})
return nil
}
func (s *ProcessingState) Ship(o *Order) error {
o.SetState(&ShippedState{})
return nil
}
func (s *ProcessingState) Deliver(o *Order) error {
return fmt.Errorf("cannot deliver: order has not been shipped")
}
// --- Shipped State ---
type ShippedState struct{}
func (s *ShippedState) String() string { return "shipped" }
func (s *ShippedState) Process(o *Order) error {
return fmt.Errorf("cannot process: order already shipped")
}
func (s *ShippedState) Cancel(o *Order) error {
return fmt.Errorf("cannot cancel: order already shipped")
}
func (s *ShippedState) Ship(o *Order) error {
return fmt.Errorf("order already shipped")
}
func (s *ShippedState) Deliver(o *Order) error {
o.SetState(&DeliveredState{})
return nil
}
// --- Delivered State ---
type DeliveredState struct{}
func (s *DeliveredState) String() string { return "delivered" }
func (s *DeliveredState) Process(o *Order) error {
return fmt.Errorf("order already delivered")
}
func (s *DeliveredState) Cancel(o *Order) error {
return fmt.Errorf("cannot cancel: order already delivered")
}
func (s *DeliveredState) Ship(o *Order) error {
return fmt.Errorf("order already delivered")
}
func (s *DeliveredState) Deliver(o *Order) error {
return fmt.Errorf("order already delivered")
}
// --- Cancelled State ---
type CancelledState struct{}
func (s *CancelledState) String() string { return "cancelled" }
func (s *CancelledState) Process(o *Order) error {
return fmt.Errorf("cannot process: order is cancelled")
}
func (s *CancelledState) Cancel(o *Order) error {
return fmt.Errorf("order already cancelled")
}
func (s *CancelledState) Ship(o *Order) error {
return fmt.Errorf("cannot ship: order is cancelled")
}
func (s *CancelledState) Deliver(o *Order) error {
return fmt.Errorf("cannot deliver: order is cancelled")
}
Использование
func main() {
o := order.NewOrder("ORD-001", []string{"Go Book", "Keyboard"}, 99.99)
fmt.Println(o.Status()) // "pending"
o.Process() // pending -> processing
o.Ship() // processing -> shipped
o.Deliver() // shipped -> delivered
// Invalid transitions return errors
if err := o.Cancel(); err != nil {
fmt.Println(err) // "cannot cancel: order already delivered"
}
}
Вендинговая машина
package vending
import "fmt"
type MachineState interface {
InsertCoin(m *Machine, amount int) error
SelectProduct(m *Machine, product string) error
Dispense(m *Machine) error
String() string
}
type Machine struct {
state MachineState
balance int
products map[string]int // product -> price
selected string
}
func NewMachine(products map[string]int) *Machine {
m := &Machine{products: products}
m.state = &IdleState{}
return m
}
func (m *Machine) SetState(s MachineState) {
m.state = s
}
func (m *Machine) InsertCoin(amount int) error { return m.state.InsertCoin(m, amount) }
func (m *Machine) SelectProduct(p string) error { return m.state.SelectProduct(m, p) }
func (m *Machine) Dispense() error { return m.state.Dispense(m) }
// --- States ---
type IdleState struct{}
func (s *IdleState) String() string { return "idle" }
func (s *IdleState) InsertCoin(m *Machine, amount int) error {
m.balance += amount
m.SetState(&HasCoinState{})
return nil
}
func (s *IdleState) SelectProduct(m *Machine, _ string) error {
return fmt.Errorf("please insert coin first")
}
func (s *IdleState) Dispense(m *Machine) error {
return fmt.Errorf("please insert coin and select product")
}
type HasCoinState struct{}
func (s *HasCoinState) String() string { return "has_coin" }
func (s *HasCoinState) InsertCoin(m *Machine, amount int) error {
m.balance += amount
return nil
}
func (s *HasCoinState) SelectProduct(m *Machine, product string) error {
price, ok := m.products[product]
if !ok {
return fmt.Errorf("product %q not available", product)
}
if m.balance < price {
return fmt.Errorf("insufficient funds: need %d, have %d", price, m.balance)
}
m.selected = product
m.SetState(&DispensingState{})
return nil
}
func (s *HasCoinState) Dispense(m *Machine) error {
return fmt.Errorf("please select a product first")
}
type DispensingState struct{}
func (s *DispensingState) String() string { return "dispensing" }
func (s *DispensingState) InsertCoin(m *Machine, _ int) error {
return fmt.Errorf("please wait, dispensing product")
}
func (s *DispensingState) SelectProduct(m *Machine, _ string) error {
return fmt.Errorf("please wait, dispensing product")
}
func (s *DispensingState) Dispense(m *Machine) error {
price := m.products[m.selected]
m.balance -= price
fmt.Printf("Dispensing %s. Change: %d\n", m.selected, m.balance)
m.selected = ""
if m.balance > 0 {
m.SetState(&HasCoinState{})
} else {
m.SetState(&IdleState{})
}
return nil
}
Когда использовать
Используйте, когда:
- Объект ведёт себя по-разному в зависимости от состояния
- Множество условных переходов (
if state == "pending"повсюду) - Конечный автомат с чёткими переходами
- Каждое состояние имеет уникальную логику
Не используйте, когда:
- Мало состояний (2-3) с простой логикой (достаточно switch)
- Состояния не определяют поведение
- Переходы тривиальны
Сравнение с похожими паттернами
| Паттерн | Отличие |
|---|---|
| Strategy | Клиент выбирает алгоритм; State переключается автоматически |
| Command | Инкапсулирует действие; State определяет поведение объекта |
| State Machine lib | Декларативное описание переходов vs ООП-подход |