建立網路層時,總會遇到這樣的時刻。
送出一個請求,不只要附加驗證權杖、留下記錄,失敗時還要重試。
把這些全塞進同一個函式後,send()不知不覺就變成 200 行。
這時就該拿出中介軟體模式了。
先說結論:在 Swift 中,使用Decorator 模式包住各項功能,再用 Chain of Responsibility依序連接這些包裝,最簡潔。兩者不是競爭關係,而是最佳搭檔。
今天就用實戰範例說明為什麼要這樣組合,以及如何用程式碼串起來。
什麼是中介軟體模式?
中介軟體是插入請求與回應之間的中間處理層。
如果你用過 Alamofire 的RequestInterceptor或伺服器端 Vapor 的Middleware,其實早就接觸過這個概念。
核心概念只有一個。
保留處理請求的核心不變,同時能在前後自由插入或移除功能。
把驗證、記錄、快取、重試等功能各自做成獨立元件。
如此一來,就能自由組合:這次請求只用驗證,下次請求則使用驗證加快取。
Decorator 和 Chain of Responsibility 有什麼不同?
很多人會搞混這兩者。
簡單總結如下。
| 區分 | Decorator | Chain of Responsibility |
|---|---|---|
| 目的 | 為既有物件包上功能 | 讓多個處理器排成一列依序通過 |
| 關係 | 包裝結構(巢狀) | 連續結構(鏈) |
| 是否通過 | 一律交給下一步 | 也可能在中途停止 |
| 在中介軟體中的角色 | 實作各個功能 | 連接並排序這些功能 |
實際使用後我發現,兩者不是對立概念,而是層次不同的設計。
Decorator 關注「如何附加功能」,Chain of Responsibility 關注「以什麼順序執行附加的功能」。
因此一起使用就能互補彼此的弱點。
實戰組合:用程式碼串起來
先定義中介軟體的共用介面。它會接收請求,再交給下一個中介軟體。
protocol Middleware {
// request接收並處理,再透過, next交給下一個步驟
func handle(_ request: Request,
next: (Request) async throws -> Response)
async throws -> Response
}
接著像 Decorator 一樣逐一建立各項功能。以下是記錄中介軟體。
struct LoggingMiddleware: Middleware {
func handle(_ request: Request,
next: (Request) async throws -> Response)
async throws -> Response {
print("➡️ 請求: \(request.url)")
let response = try await next(request) // 交給下一個步驟
print("⬅️ 回應: \(response.status)")
return response
}
}
這裡呼叫next(request)的部分,就是 Chain of Responsibility 的鏈結。
完成自己的工作(寫入記錄)後,再把其餘工作交給下一個中介軟體。
最後,將多個中介軟體折疊成一條鏈。
func buildChain(_ middlewares: [Middleware],
final: @escaping (Request) async throws -> Response)
-> (Request) async throws -> Response {
middlewares.reversed().reduce(final) { next, mw in
{ req in try await mw.handle(req, next: next) }
}
}
重點是使用reduce從後方開始包裝。
如果依[인증, 로깅, 재시도]順序放入陣列,請求會依該順序通過,回應則會反向離開。想像洋蔥皮就很貼切。
這樣設計有什麼好處?
整理一下我套用到實際專案後感受到的優點。
-
新增功能變成在陣列中加入一行。 需要快取時,只要把
CachingMiddleware()插入陣列即可。 -
**順序很容易調整。**要讓驗證先於記錄還是後於記錄執行,只要改變陣列順序。
-
**測試更方便。**每個中介軟體都彼此獨立,可以逐一加入單元測試。
-
**核心變乾淨了。**原本 200 行的
send()又回到十行以內。
相對地,也有需要注意的地方。
中介軟體太多時,很難追蹤一個請求究竟會經過哪些地方。
所以當鏈超過五、六個時,我會把記錄中介軟體放在最前面,記下通過路徑。
總結
用 Decorator 建立功能,再用 Chain of Responsibility 排定順序的組合,熟悉後不只適用於網路層,也能直接用在事件處理、驗證等場景。
建議把今天的範例接到一個小型玩具專案中。親手剝開每一層,概念會更快內化。祝你重構愉快!

