一個什麼都做的 God Object 怎麼辨識、怎麼拆?

Medium★★★★★Studied

FromRefactoring for Software Design SmellsAgile Principles, Patterns, and Practices

Concepts practiced📏 單一職責原則 (SRP)📏 介面隔離原則 (ISP)

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

📚 From the Books

一個什麼都做的 God Object 怎麼辨識、怎麼拆?ProblemMedium反模式辨識

有一個類別包山包海:欄位一大票、方法幾百個、什麼流程都經過它。改任何小需求都得動它,動它又到處出事。這是 God Object(上帝物件)——辨識它、然後怎麼拆?

🎯 Scenario

// 一個 OrderManager 同時扛下驗證、計價、庫存、金流、通知、持久化……
class OrderManager {
    fun validate(order: Order) { /* ... */ }
    fun calculatePrice(order: Order): Int { /* ... */ }
    fun reserveStock(order: Order) { /* ... */ }
    fun charge(order: Order) { /* ... */ }
    fun sendConfirmation(order: Order) { /* ... */ }
    fun save(order: Order) { /* ... */ }
    // ...再過半年,方法破百、欄位破百
}

壞味道:臃腫介面(公開成員過多)+臃腫實作(方法多且複雜)。書中的極端真實案例 java.awt.Component 有 332 個方法、107 個欄位、上萬行——所有方法的循環複雜度總和 1143。集中式控制讓這個類別成為系統的單點腐化源。

🧠 Intuition

TIP

先別看下面。問自己:這個類別有「幾個會要求它改變的人(actor)」?驗證規則、計價策略、金流串接、通知渠道——它們會不會各自因不同理由而變?如果答案是「會」,那它承擔了幾個職責?

⚖️ Reference Design

先分辨:它是『責任太多』還是『單純太大』?

書中把兩種味道分清楚——它們常同時出現但不等同:

味道違反焦點
Multifaceted AbstractionSRP一個抽象多個責任
Insufficient Modularization分解原則一個抽象過大、過於複雜
  • 「一個類別可以只有一個責任但實作很龐大;也可以很小卻扛多個責任。」先判斷你的 God Object 屬於哪一種(多半兩者兼有)。
解法:沿變更軸線把職責切出去
// 沿『誰會要求改』把 OrderManager 拆成一組協作者
class OrderValidator { fun validate(order: Order) { /* ... */ } }
class Pricing        { fun calculate(order: Order): Int { /* ... */ } }
class Inventory      { fun reserve(order: Order) { /* ... */ } }
class PaymentGateway { fun charge(order: Order) { /* ... */ } }
class Notifier       { fun confirm(order: Order) { /* ... */ } }
class OrderRepository{ fun save(order: Order) { /* ... */ } }

// OrderService 只負責『編排』,把工作委派出去(瘦身後的協調者)
class OrderService(
    private val validator: OrderValidator,
    private val pricing: Pricing,
    private val inventory: Inventory,
    private val payment: PaymentGateway,
    private val notifier: Notifier,
    private val repo: OrderRepository,
) {
    fun place(order: Order) {
        validator.validate(order)
        val total = pricing.calculate(order)
        inventory.reserve(order)
        payment.charge(order)
        repo.save(order)
        notifier.confirm(order)
    }
}
  • SRP 把「不同變更理由」切到不同類別;用組合注入把它們接回來。
  • 若拆出去的介面仍很胖、被不同客戶端共用,再用 ISP 切成客戶端專屬介面。
  • 別把 OrderService 又養成新的 God Object——它只做編排,不含業務細節。

🔑 Takeaways

✍️ My Notes

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

📖 Further Reading

🔗 Dive Deeper