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 — algoritma dipilih dari luar oleh client.
sort.setStrategy(quickSort). Client memilih. - State — state berubah sebagai konsekuensi dari aksi internal.
order.pay()→ status jadi "paid" otomatis. Object yang mengatur transisi state-nya sendiri.
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:
- XState (npm) — library state machine populer untuk React/Vue/Node. Secara formal implement State Pattern + finite state machine. Visualizer keren, cocok untuk workflow UI kompleks (checkout wizard, video player, form multi-step).
- Redux Toolkit slices — reducer pattern adalah flat version of state machine. Tiap action valid di state tertentu.
- Game entity state — Unity, Phaser, PixiJS sering pakai State Pattern untuk enemy AI (Idle → Patrol → Chase → Attack → Dead).
- Connection state — WebSocket reconnection: Connecting → Open → Closing → Closed → Reconnecting. Library
reconnecting-websocketimplement ini. - Laravel model state (Spatie package
spatie/laravel-model-states) — menyimpan state di kolom DB + transisi terkontrol.
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:
- Object punya state berbeda yang ubah perilaku signifikan (bukan sekadar ubah data)
- Ada transisi valid/tidak valid yang harus ditegakkan (tidak bisa ship order yang belum dibayar)
- Lifecycle kompleks yang kalau ditulis pakai if/else akan spread ke banyak method
Kapan JANGAN pakai:
- Cuma 2 state sederhana — boolean flag sudah cukup
- Tidak ada perilaku yang berbeda antar state, hanya nilai yang berubah — pakai property biasa
- State machine flat tanpa logic transisi ketat — enum + switch sudah ok
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.