業務程式一邊跑邏輯,一邊在
if-else/when裡決定要new哪個具體類別,同樣的建立碼還在好幾個呼叫端重複出現。要換實作或加一種型別,就得回頭改一堆地方。這不是「該建哪個行為」的問題,而是「物件由誰、在哪裡被建立」的問題——怎麼重構?
🎯 Scenario
interface Repo { fun insert(o: Order) }
class SqlRepo : Repo { override fun insert(o: Order) { /* ... */ } }
class FileRepo : Repo { override fun insert(o: Order) { /* ... */ } }
class OrderService {
fun save(o: Order) {
val repo = SqlRepo() // 使用方直接 new 具體類別,綁死實例化策略
repo.insert(o)
}
}
class ReportJob {
fun run() {
val repo = SqlRepo() // 同樣的建立碼又出現一次
// ...
}
}
// 或者:依型別建立的 when 散落各處
fun makeRepo(type: String): Repo = when (type) {
"sql" -> SqlRepo()
"file" -> FileRepo()
else -> error("unknown repo: $type")
}
壞味道:使用方一邊執行業務邏輯、一邊在分支裡決定 new 哪個類別,把「使用」和「建立」兩件事混在一起,凝聚變弱、過早綁死在某種實例化策略;同樣的建立知識重複散落在多個呼叫端。
🧠 Intuition
TIP
先別看下面。把問題切成兩半:使用方真正需要的是抽象介面(Repo),還是某個具象類別(SqlRepo)?如果「決定 new 哪一個」是唯一容易變化的部分,能不能把這個決定從使用方抽出去、集中管理,讓使用方眼裡只剩抽象?
⚖️ Reference Design
這頁是「建立」的散落,不是「行為」的分支
先劃清界線,別跟 以多型取代型別 switch 混淆:
- 物件導向世界裡,「使用其他物件」與「建立/管理其他物件」是兩件不同的事。新手常把兩者混在一起——程式一邊跑業務邏輯、一邊在
if-else裡決定 new 哪個類別。 - 一條基本守則:一個物件要嘛使用其他物件,要嘛建立/管理其他物件,不能同時兩者。
- 所以判準是:分支決定的是「該執行哪種行為」→ 那是多型 / Strategy;分支決定的是「該實例化哪個類別」→ 那是工廠。本頁處理後者。
第一步:Simple Factory —— 把建立集中到一處
把「該實例化哪一個子類別」這個容易變化的決定,從使用方抽出來、封裝進一個獨立的工廠:
object RepoFactory {
fun create(type: String): Repo = when (type) { // switch 仍在,但只剩這一處
"sql" -> SqlRepo()
"file" -> FileRepo()
else -> error("unknown repo: $type")
}
}
// 使用方只認識 Repo 介面,不再認識具象類別
class OrderService(private val repo: Repo) {
fun save(o: Order) = repo.insert(o)
}- 使用方不需依賴具體產品,給一個 key 就能拿到對應實例;建立知識收斂到一處,重複消失。
- 但缺點也清楚:每新增一種產品,都要回頭改
create的when——既對擴展開放,也對修改開放,違反開放封閉原則。
第二步:Factory Method —— 把「該建哪個」下放到子類,消掉 switch
再進一步:定義一個建立物件的介面,讓子類別決定要實例化哪一個類別,把實例化延遲到子類:
interface RepoFactory { fun create(): Repo }
class SqlRepoFactory : RepoFactory { override fun create() = SqlRepo() }
class FileRepoFactory : RepoFactory { override fun create() = FileRepo() }
// 使用方只透過抽象工廠與抽象產品協作
class OrderService(private val factory: RepoFactory) {
fun save(o: Order) = factory.create().insert(o)
}- 不再有一個含
switch的工廠類。新增一種產品,只需新增一個產品類 + 一個工廠類,不必修改原有的工廠——整個體系對擴展開放、對修改關閉,符合 OCP,也消除了對具體類別的綁定(程式只處理Repo介面)。 - 代價:每多一種產品就要多一個工廠類,開發量增加;而且「選擇要用哪個工廠」的判斷依然存在,只是從工廠內部移到了使用方那一處。
- 這也是框架的招牌用法:框架活在抽象層次,不知也不該知具體型別,就用 Factory Method 把「具體實例化」交還給用框架的人。詳見 Factory Method。
Simple Factory vs. Factory Method:何時用哪個
| Simple Factory | Factory Method | |
|---|---|---|
| 分支判斷 | 集中在工廠類的 switch | 下放到使用方「選哪個工廠」 |
| 新增產品 | 要改工廠類(違反 OCP) | 只需新增類別(符合 OCP) |
| 成本 | 一個工廠類,簡單 | 每種產品多一個工廠類 |
- Factory Method 是 Simple Factory 的進一步抽象與推廣:克服了它違反 OCP 的缺點,同時保住「封裝物件建立過程」的優點。
- 但它並沒有讓「選哪個」的判斷消失,只是搬了家。先用 Simple Factory 集中,等擴充壓力真的出現、
switch開始頻繁被改,再升級成 Factory Method。
和 DI 的關係:工廠回答「誰負責建立」
🔑 Takeaways
- 「到處
new具體類別 / 建立用的when散落」是把「使用」和「建立」混在一起——先把建立知識抽離、集中。 - 兩步走:Simple Factory 把建立收斂到一處(仍有一個 switch);擴充壓力出現時升級 Factory Method,把「該建哪個」下放子類、消掉 switch、對擴充開放。
- 判準區隔:分支決定「該做哪種行為」→ 多型 / Strategy;分支決定「該建哪個類別」→ 工廠。
- 一物件要嘛用、要嘛造,不能兩者皆是——這條守則同時打通了工廠與 DIP / OCP。
No notes yet — jot your takeaways or Q&A here.