一個不斷長大的 type switch 要怎麼解?

Medium★★★★★Studied

From大話設計模式Agile Principles, Patterns, and Practices

Concepts practiced🔁 策略 (Strategy)📏 開放封閉原則 (OCP)

Convention: read the requirements & constraints first, sketch your own design, then expand the reference to compare.

📚 From the Books

一個不斷長大的 type switch 要怎麼解?ProblemMedium重構情境

程式裡有個依「型別字串」分支的大 when,每加一種型別就要回來改它,而且同樣的 when(type) 在好幾個地方重複出現。要怎麼重構?

🎯 Scenario

fun fee(type: String, amount: Int): Int = when (type) {
    "regular" -> amount
    "member"  -> (amount * 0.9).toInt()
    "vip"     -> (amount * 0.8).toInt()
    // 每加一種會員等級,就要回來改這裡……而且:
    else      -> error("unknown type")
}

fun label(type: String): String = when (type) {   // 同樣的 when 又出現一次
    "regular" -> "一般"
    "member"  -> "會員"
    "vip"     -> "VIP"
    else      -> "未知"
}

壞味道:新增一型要改多處(違反開放封閉原則 OCP)、when(type) 散落重複else -> error 把編譯期該抓的漏掉延到執行期。

🧠 Intuition

TIP

先別看下面。問自己:這些分支的「型別」其實是什麼?如果把每一種型別變成一個物件,這些重複的 when 會發生什麼事?你會用哪個 pattern?

⚖️ Reference Design

答案:用多型 / 策略消滅 switch

把「型別」提升成一個帶行為的物件,讓分支邏輯各自歸位:

enum class Tier(val label: String, val discount: Double) {
    REGULAR("一般", 1.0),
    MEMBER ("會員", 0.9),
    VIP    ("VIP", 0.8);

    fun fee(amount: Int) = (amount * discount).toInt()
}

// 呼叫端不再有任何 when:
Tier.VIP.fee(1000)   // 800
Tier.VIP.label       // "VIP"
  • 行為 / 資料跟著型別走,新增一型只改一個地方(加一個 enum 常數),其餘程式不動 → 滿足 OCP。
  • 若各型別行為較複雜或要在執行期注入,就升級成 Strategyinterface PricingStrategy + 多個實作。
選策略的地方也別變成新的 switch
  • 從外部輸入(字串 / 設定)對應到策略物件時,用查表 / 註冊表而不是又一個 when
val byKey = Tier.entries.associateBy { it.name.lowercase() }
val tier = byKey[input] ?: error("unknown tier")
  • 否則只是把 when 從「行為處」搬到「建立處」,味道沒消掉,只是換地方(見 Strategy 頁的誤用一節)。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading

🔗 Dive Deeper