在協作物件之間攔截呼叫,不改動任何一方就豐富或改變行為——這是 DI 的三大維度之一(另兩者是 Object Composition 與 Object Lifetime)。它的核心機制就是 Decorator:包一個實作相同抽象的物件,在前後加料。把攔截推到極致,就是 AOP(橫切關注點的統一處理)——而且不必靠框架,靠好設計就能長出來。
🧠 When to Use
TIP
訊號:logging、稽核、授權、交易、快取這類邏輯散落在每個業務類別裡,跟真正的業務邏輯攪在一起——這些「不屬於任何單一業務」的功能,就是該被攔截抽離的橫切關注點。
- 想在既有行為前後加料(記錄、驗權、重試),又不想改被加料的類別。
- 橫切關注點(cross-cutting concern)跨越多個模組,重複出現在每個實作裡。
- 已經遵循 DI 原則(依賴抽象、鬆耦合、LSP),攔截幾乎是免費附贈的能力。
⚖️ 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
- 裝飾器改了介面:一旦想「順便多開一個方法」,就不再能透明替換——那是 Adapter 或新抽象,不是 Decorator,也就無法被攔截鏈統一處理。
- 組裝順序隨手排:授權若排在稽核內層,未授權操作仍會被記錄甚至打到 DB——順序是行為的一部分。
- 為橫切關注點寫 N 個專屬 Decorator:介面太寬時,每個方法、每個 Service 都要各寫一份攔截碼;正解是先用 ISP 拆成泛型抽象,再寫一個泛型 Decorator。
- 在 Composition Root 以外組裝:攔截鏈散在業務碼裡,就退化成到處手工包裝,失去集中管理的意義。
🔑 Takeaways
- Interception=在協作物件間攔截呼叫、不改任一方就加料,是 DI 三大維度之一,核心機制是 Decorator。
- 橫切關注點(logging、稽核、授權、交易、快取、斷路器)交給裝飾鏈,業務邏輯保持乾淨;組裝與順序都在 Composition Root 決定。
- AOP 可以靠設計而非工具達成:ISP 把介面縮成泛型抽象,一個泛型 Decorator 就能一次實作、全面套用、保有編譯期安全。
- 若程式已遵循 DI 原則,攔截幾乎免費——這也是 DI 容器把 interception 列為賣點的原因。
No notes yet — jot your takeaways or Q&A here.