MidТеория5 min

State

Паттерн State в Go: конечные автоматы через интерфейсы, статусы заказов и вендинговые машины

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 ООП-подход

Проверь себя

Как паттерн State устраняет большие switch-конструкции?

Чем State отличается от Strategy?