It allows an object to change its behavior when its internal state changes.

Components

SOLID principles

Implementation

Composition + Interface Polymorphism

// State
interface State {
  insertCoin(turnstile: Turnstile): void;
  push(turnstile: Turnstile): void;
}

class LockedState implements State {
  insertCoin(turnstile: Turnstile): void {
    console.log("Coin inserted. Turnstile unlocked.");
    turnstile.setState(turnstile.unlockedState);
  }
  push(turnstile: Turnstile): void {
    console.log("Turnstile is locked. Can't push.");
  }
}

class UnlockedState implements State {
  insertCoin(turnstile: Turnstile): void {
    console.log("Already unlocked. Return coin.");
  }
  push(turnstile: Turnstile): void {
    console.log("Turnstile pushed. Locking turnstile.");
    turnstile.setState(turnstile.lockedState);
  }
}

// Context
class Turnstile {
  lockedState: State;
  unlockedState: State;
  private currentState: State;

  constructor() {
    this.lockedState = new LockedState();
    this.unlockedState = new UnlockedState();
    this.currentState = this.lockedState;
  }

  setState(state: State): void {
    this.currentState = state;
  }

  insertCoin(): void {
    this.currentState.insertCoin(this);
  }

  push(): void {
    this.currentState.push(this);
  }
}

// Client Code
const turnstile = new Turnstile();
turnstile.push();        // Turnstile is locked. Can't push.
turnstile.insertCoin();  // Coin inserted. Turnstile unlocked.
turnstile.push();        // Turnstile pushed. Locking turnstile.

Go Composition + Interface Polymorphism

// State
type State interface {
	InsertCoin(t *Turnstile)
	Push(t *Turnstile)
}

type LockedState struct{}

func (l *LockedState) InsertCoin(t *Turnstile) {
	fmt.Println("Coin inserted. Turnstile unlocked.")
	t.SetState(t.unlockedState)
}

func (l *LockedState) Push(t *Turnstile) {
	fmt.Println("Turnstile is locked. Can't push.")
}

type UnlockedState struct{}

func (u *UnlockedState) InsertCoin(t *Turnstile) {
	fmt.Println("Already unlocked. Return coin.")
}

func (u *UnlockedState) Push(t *Turnstile) {
	fmt.Println("Turnstile pushed. Locking turnstile.")
	t.SetState(t.lockedState)
}

// Context
type Turnstile struct {
	lockedState   State
	unlockedState State
	currentState  State
}

func NewTurnstile() *Turnstile {
	t := &Turnstile{}
	t.lockedState = &LockedState{}
	t.unlockedState = &UnlockedState{}
	t.currentState = t.lockedState
	return t
}

func (t *Turnstile) SetState(state State) {
	t.currentState = state
}

func (t *Turnstile) InsertCoin() {
	t.currentState.InsertCoin(t)
}

func (t *Turnstile) Push() {
	t.currentState.Push(t)
}

// Client Code
turnstile := NewTurnstile()
turnstile.Push()       // Turnstile is locked. Can't push.
turnstile.InsertCoin() // Coin inserted. Turnstile unlocked.
turnstile.Push()       // Turnstile pushed. Locking turnstile.

When to Set State Manually?

Unrelated States

If we find that you're introducing entirely unrelated behaviors—like turning lights on and off—it usually means you're dealing with a different concern or domain within your system.