Replace Conditional with Polymorphism:switch 不會消失,只會收斂到 factory
最容易誤會的地方:改成多型之後 switch 並沒有不見,它搬到 factory 去了。真正的收益是——整個系統只剩一個 switch,而不是每多一個行為就再抄一份同樣的 switch。
用法一:switch 在分派「型別」
// before
function plumage(bird) {
switch (bird.type) {
case 'EuropeanSwallow': return 'average'
case 'AfricanSwallow': return bird.numberOfCoconuts > 2 ? 'tired' : 'average'
case 'NorwegianBlueParrot': return bird.voltage > 100 ? 'scorched' : 'beautiful'
default: return 'unknown'
}
}// after:每個型別一個 class,case 的內容搬進去
class Bird {
constructor(data) { Object.assign(this, data) }
get plumage() { return 'unknown' }
}
class EuropeanSwallow extends Bird {
get plumage() { return 'average' }
}
class AfricanSwallow extends Bird {
get plumage() { return this.numberOfCoconuts > 2 ? 'tired' : 'average' }
}
class NorwegianBlueParrot extends Bird {
get plumage() { return this.voltage > 100 ? 'scorched' : 'beautiful' }
}判斷「要不要用這招」的訊號:同一組 case 標籤在多個 function 裡重複出現(plumage、airSpeedVelocity…)。只有一個 function 用到時,switch 本身沒問題。
factory 可以用 map 取代 switch
我更喜歡這種寫法——新增一種鳥只動資料,不動流程:
const BIRD_MAP = {
EuropeanSwallow,
AfricanSwallow,
NorwegianBlueParrot,
}
function createBird(data) {
const BirdClass = BIRD_MAP[data.type] ?? Bird
return new BirdClass(data)
}同一個形狀在實務上的完整版(註冊表 + Strategy)見 註冊表分派 side effect(Registry + Strategy Handler)。
用法二:基本情況 + 少數變體
不是分派型別,而是「大部分走通則,某些情況例外」。通則留在基底類別,例外只覆寫真正不同的那幾個方法:
class Rating {
get value() {
const vpf = this.voyageProfitFactor
const vr = this.voyageRisk
const chr = this.captainHistoryRisk
return (vpf * 3 > (vr + chr * 2)) ? 'A' : 'B'
}
get captainHistoryRisk() { return this.history.length < 5 ? 5 : 1 }
get voyageProfitFactor() { return this.voyage.zone === 'china' ? 3 : 2 }
}
class ExperiencedChinaRating extends Rating {
get captainHistoryRisk() { return Math.max(super.captainHistoryRisk - 2, 0) }
get voyageProfitFactor() {
return super.voyageProfitFactor + 3 + (this.history.length > 10 ? 1 : 0)
}
}
function createRating(voyage, history) {
return (voyage.zone === 'china' && hasChina(history))
? new ExperiencedChinaRating(voyage, history)
: new Rating(voyage, history)
}重點一樣:voyage.zone === 'china' && hasChina(history) 這個判斷從三個地方收斂成一個,其他地方只要問「我是哪一種 Rating」。
代價
型別只有兩種、而且不會再增加時,開一層 class 階層是過度設計——每個變體都要繼承,程式會變長。這招換來的是「新增型別不用改既有流程」,沒有這個需求就不划算。