需求:一張訂單會在「草稿 → 已付款 → 出貨中 → 完成 / 取消」之間流轉。每個狀態能做的操作不同(草稿能改、已付款不能改能退款、完成後只能查),而且每個動作都要檢查「現在這個狀態允不允許」。用一堆
if (status == ...)很快就失控。怎麼設計?
🎯 Scenario
// 反例:每個操作都散落一組狀態檢查,狀態一多就爆炸
class Order(var status: String) {
fun pay() {
if (status != "draft") error("can't pay in $status")
status = "paid"
}
fun ship() {
if (status != "paid") error("can't ship in $status") // 同樣的分支到處複製
status = "shipped"
}
fun cancel() {
if (status == "shipped" || status == "done") error("too late")
status = "cancelled"
}
}
需求關鍵字:物件行為隨『內部狀態』改變、狀態間有明確轉移規則、且是狀態自己決定下一步。這是 State 的主場。
🧠 Intuition
TIP
先別看下面。注意這裡切換的不是「演算法」而是「狀態」,而且是狀態自己決定能不能轉、轉去哪。把「每個狀態允許什麼、之後變成什麼」封裝進各自的狀態物件,Order 只把操作委派給當前狀態。
⚖️ Reference Design
主解法 — State:一狀態一物件,轉移封裝在狀態內
interface OrderState {
fun pay(o: Order): OrderState = error("can't pay in ${this::class.simpleName}")
fun ship(o: Order): OrderState = error("can't ship in ${this::class.simpleName}")
fun cancel(o: Order): OrderState = error("can't cancel now")
}
object Draft : OrderState {
override fun pay(o: Order) = Paid // 狀態自己決定下一個狀態
override fun cancel(o: Order) = Cancelled
}
object Paid : OrderState {
override fun ship(o: Order) = Shipped
override fun cancel(o: Order) = Refunding
}
object Shipped : OrderState { /* 只允許收貨 → Done */ }
class Order(var state: OrderState = Draft) {
fun pay() { state = state.pay(this) } // Order 只委派,不含分支
fun ship() { state = state.ship(this) }
}- 每個狀態把「允許哪些操作、之後轉到哪」封裝在自己身上;
Order不再有if (status == ...)。 - 加一個新狀態 = 新增一個
OrderState實作,不必回頭改所有操作(OCP)。 - 介面用預設實作統一「此狀態不允許此操作」的行為,非法轉移集中一處。
State vs Strategy —— 結構同形,差在誰決定切換
- 同構:兩者都是「介面 + 多實作 + 委派」。
- 差別:Strategy 由外部選定一個演算法、存活期通常不自換;State 是狀態物件自己決定下一個狀態、行為隨內部狀態流轉。
- 若你的「狀態」其實只是「外部挑的一種計算方式、彼此不會互相轉移」——那要的是 Strategy,不是 State。
邊界與誤用
- 狀態超多、轉移是資料 → 若轉移表龐大且規則化,考慮用狀態轉移表(map 驅動)而非一堆類別;State 適合「每狀態行為差異大」的情況。
- 只是把 switch 搬家 → 若選狀態的地方還是一個巨大
when(status)在 new 狀態物件,味道只是搬到工廠;用註冊表/查表對應。(見重構大 switch) - 狀態物件亂存業務資料 → 狀態物件最好無狀態(可做
object單例),把資料留在 context(Order)上。
🔑 Takeaways
- 「行為隨內部狀態改變 + 明確轉移規則 + 狀態自己決定下一步」→ 用 State:一狀態一物件、轉移封裝在狀態內、context 只委派。
- 和 Strategy 同構,差在「誰決定切換」——外部挑選是 Strategy,內部流轉是 State。
- 轉移規則龐大且資料化 → 改用狀態轉移表;別讓選狀態的地方又變成大 switch。
No notes yet — jot your takeaways or Q&A here.