攔截與 AOP (Interception)

★★★★Studied

FromDependency Injection

📚 From the Books

攔截與 AOP (Interception)ConceptDI & IoC

在協作物件之間攔截呼叫,不改動任何一方就豐富或改變行為——這是 DI 的三大維度之一(另兩者是 Object Composition 與 Object Lifetime)。它的核心機制就是 Decorator:包一個實作相同抽象的物件,在前後加料。把攔截推到極致,就是 AOP(橫切關注點的統一處理)——而且不必靠框架,靠好設計就能長出來。

🧠 When to Use

TIP

訊號:logging、稽核、授權、交易、快取這類邏輯散落在每個業務類別裡,跟真正的業務邏輯攪在一起——這些「不屬於任何單一業務」的功能,就是該被攔截抽離的橫切關注點。

⚖️ Structure & Variants

Decorator —— 攔截的最直接手段
// 裝飾者實作與被裝飾者相同的抽象,委派給 decoratee 並在前後加料
class AuditingProductRepository(
    private val decoratee: ProductRepository,   // 被裝飾者(Decoratee)
    private val auditTrail: AuditTrail,
) : ProductRepository {
    override fun update(product: Product) {
        auditTrail.record("Updating product ${product.id}")
        decoratee.update(product)               // 委派給內層
    }
}
  • 三個特性:實作相同抽象、建構子接收 Decoratee、方法內委派並在前後加邏輯。
  • 鏈式串接(如俄羅斯套娃),多層 Decorator 層層包裹。
  • 被裝飾的實作完全不知道攔截存在——這正是鬆耦合的回報。
典型橫切關注點與現成裝飾器
  • Auditing:每次操作前後記錄稽核軌跡(誰、何時、做了什麼)。
  • Circuit Breaker(斷路器):追蹤對外部服務的失敗次數,超過閾值就「斷路」,狀態在 Closed(正常)→ Open(斷路)→ Half-Open(試探恢復)間轉換,避免下游故障連鎖崩潰。
  • Exception Reporting:捕捉例外、回報監控系統,然後重新拋出(不吞例外)。
  • Security / Authorization:執行操作前檢查當前使用者權限,把授權邏輯從業務邏輯徹底分離。
組裝順序在 Composition Root 決定
// 所有 Decorator 都在 Composition Root 串接
val repository: ProductRepository =
    SecurityProductRepository(          // 最外層:授權檢查
        AuditingProductRepository(      // 第二層:稽核
            CircuitBreakerRepository(   // 第三層:斷路器
                SqlProductRepository(connectionString), // 核心實作
            ),
            auditTrail,
        ),
        authService,
    )
  • 順序有意義:授權放最外層,未授權的操作就不會觸發稽核或實際 DB 呼叫。
  • 唯一該組裝的地方是 Composition Root
AOP by design —— 從 ISP 長出泛型 Decorator

當介面遵循 ISP 被縮到極致(一個方法一個介面),就會自然趨向泛型抽象;一個泛型 Decorator 便能套用到所有 Command / Query,取代數十個專屬 Decorator。

// 把操作拆成獨立 Command,用泛型介面統一
interface CommandHandler<TCommand> { fun handle(command: TCommand) }

// 一個泛型 Decorator 套用到「所有」Command
class AuditingCommandHandler<TCommand>(
    private val decoratee: CommandHandler<TCommand>,
    private val auditTrail: AuditTrail,
) : CommandHandler<TCommand> {
    override fun handle(command: TCommand) {
        auditTrail.record(command)     // 記錄
        decoratee.handle(command)      // 委派
    }
}
  • 效果:一次實作、全面套用、編譯期安全、無需額外工具
  • 三種 AOP 途徑的取捨:**SOLID 驅動(設計導向)**保留編譯期檢查、可維護性高,但需重新設計介面,對 Legacy Code 較難;Dynamic Interception 容易加入、工具成熟,但失去編譯期支援;Compile-time Weaving 對 Legacy 容易套用,但與 DI 理念背道而馳。書中最推薦第一種。

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

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

📖 Further Reading