State Pattern — Design Patterns

State Pattern membuat object mengubah perilakunya saat internal state-nya berubah. Seolah-olah object "berganti class" di runtime. Cocok untuk state machine: or

State Pattern membuat object mengubah perilakunya saat internal state-nya berubah. Seolah-olah object "berganti class" di runtime. Cocok untuk state machine: order status, game character, workflow engine.

Problem yang diselesaikan:

Kode dengan state sering penuh if/else panjang yang ngulang di setiap method:

class Order {
  cancel() {
    if (this.status === "pending") { /* ... */ }
    else if (this.status === "paid") { /* refund flow */ }
    else if (this.status === "shipped") { throw new Error("Too late"); }
    else if (this.status === "delivered") { throw new Error("Cannot cancel"); }
  }

  ship() {
    if (this.status === "pending") throw new Error("Not paid");
    else if (this.status === "paid") { /* ... */ }
    else if (this.status === "shipped") throw new Error("Already shipped");
    // ...
  }

  deliver() { /* lagi-lagi if/else panjang */ }
}

Tiap method punya switch yang mirip. Tambah state baru = edit semua method. State Pattern memindahkan logic per-state ke class terpisah.

Strategy vs State — apa bedanya?

Keduanya secara struktur hampir sama (delegasi ke object behavior). Bedanya di niat:

Strategy = "pilihan kamu". State = "konsekuensi hidup object".

Implementasi minimal — Order state machine:

// Tiap state adalah class kecil dengan method yang sama
class PendingState {
  pay(order)     { order.setState(new PaidState()); }
  cancel(order)  { order.setState(new CancelledState()); }
  ship(order)    { throw new Error("Order belum dibayar"); }
  deliver(order) { throw new Error("Order belum dikirim"); }
}

class PaidState {
  pay(order)     { throw new Error("Sudah dibayar"); }
  cancel(order)  { order.refund(); order.setState(new CancelledState()); }
  ship(order)    { order.setState(new ShippedState()); }
  deliver(order) { throw new Error("Belum dikirim"); }
}

class ShippedState {
  pay(order)     { throw new Error("Sudah dibayar"); }
  cancel(order)  { throw new Error("Terlambat untuk membatalkan"); }
  ship(order)    { throw new Error("Sudah dikirim"); }
  deliver(order) { order.setState(new DeliveredState()); }
}

class DeliveredState {
  pay(order)     { throw new Error("Sudah selesai"); }
  cancel(order)  { throw new Error("Tidak bisa dibatalkan"); }
  ship(order)    { throw new Error("Sudah dikirim"); }
  deliver(order) { throw new Error("Sudah diterima"); }
}

class CancelledState {
  pay(_)     { throw new Error("Order dibatalkan"); }
  cancel(_)  { throw new Error("Sudah dibatalkan"); }
  ship(_)    { throw new Error("Tidak bisa dikirim"); }
  deliver(_) { throw new Error("Tidak bisa dikirim"); }
}

class Order {
  constructor() {
    this.state = new PendingState();
  }
  setState(state) { this.state = state; }
  pay()           { this.state.pay(this); }
  cancel()        { this.state.cancel(this); }
  ship()          { this.state.ship(this); }
  deliver()       { this.state.deliver(this); }
}

const order = new Order();
order.pay();     // pending → paid
order.ship();    // paid → shipped
order.deliver(); // shipped → delivered

Transisi valid di-encode langsung di state class. Tambah state baru = tambah class baru, tidak sentuh state lain (kecuali transisi ke/dari state baru).

Dunia nyata di ekosistem JS:

Variasi: transisi dengan validator / hooks:

Di sistem real, setiap transisi sering punya hooks: onEnter, onExit, canTransition. Library seperti XState punya ini built-in.

Kapan pakai:

Kapan JANGAN pakai:

Trade-off:

State menambah kelas per state, tapi drastis rapikan kode dengan state transition kompleks. Alternatif modern: discriminated union + exhaustive switch di TypeScript, atau library state machine seperti XState yang kasih visualizer gratis. Pilih berdasarkan tim & kompleksitas.

Yang akan kamu pelajari