Patterns: behavior · 3 MIN READ
State: behavior follows a valid lifecycle
Represent legal transitions instead of scattered boolean combinations.
Represent legal transitions instead of scattered boolean combinations. State makes behavior depend on an explicit lifecycle state. Trace the worked Go example, then compare the benefit with the cost before applying this design to your own project.
Behavioral pattern · A MAP OF THE IDEA
- Current state limits operations
- Transition validates its precondition
- New state changes allowed behavior
Useful when
- Lifecycle rules are scattered across conditionals
- Invalid transitions should be explicit errors
Watch out
- Persistence needs concurrency and retry rules
- Separate state types can overcomplicate a small enum
Implementation & operation cost: Medium
Fit depends on the task
Editorial suitability, not benchmarks or a universal ranking. Styles describe different axes and can be combined.
The design decision
State makes behavior depend on an explicit lifecycle state. A finite-state switch is often the simplest Go representation; separate state types help when each state owns substantial behavior. Write allowed transitions down so invalid combinations cannot quietly become normal.
A complete Go example
Read this program from the entry point and follow the calls shown in the diagram. The output is included at the end so you can check the behavior without a compiler.
package main
import (
"errors"
"fmt"
)
type Phase int
const (
Draft Phase = iota
Submitted
Approved
)
type Review struct{ phase Phase }
func (r *Review) Submit() error {
if r.phase != Draft {
return errors.New("not a draft")
}
r.phase = Submitted
return nil
}
func (r *Review) Approve() error {
if r.phase != Submitted {
return errors.New("not submitted")
}
r.phase = Approved
return nil
}
func main() {
r := &Review{}
fmt.Println(r.Approve())
fmt.Println(r.Submit())
fmt.Println(r.Approve())
}
// Output:
// not submitted
// <nil>
// <nil>
Approve rejects a transition from Draft, then succeeds after Submit. One enum avoids contradictory submitted and approved booleans. This demonstrates the state-machine intent; the classic object State pattern delegates to separate state objects when their behaviors justify it.
Tradeoffs and the simpler alternative
Persisted state machines need concurrency control, transition audit and idempotency. Do not create a type for each enum value when a short switch is clear. State varies because lifecycle changes; Strategy is typically selected as an interchangeable policy by a caller.
TAKE THIS WITH YOU
Restricts behavior to valid transitions from current state. Persisted state machines need concurrency control, transition audit and idempotency. Do not create a type for each enum value when a short switch is clear. State varies because lifecycle changes; Strategy is typically selected as an interchangeable policy by a caller.
Check your understanding
Which statement best describes State: behavior follows a valid lifecycle?
Go to the source
Progress stays in this browser. No account needed.