軟體設計

Swift 中介軟體模式:Decorator 與 Chain of Responsibility 的實戰組合

建立網路層時,總會遇到這樣的時刻。

閱讀 4 分鐘
Swift 中介軟體模式:Decorator 與 Chain of Responsibility 的實戰組合 封面圖

建立網路層時,總會遇到這樣的時刻。

送出一個請求,不只要附加驗證權杖、留下記錄,失敗時還要重試。

把這些全塞進同一個函式後,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從後方開始包裝。

如果依[인증, 로깅, 재시도]順序放入陣列,請求會依該順序通過,回應則會反向離開。想像洋蔥皮就很貼切。

請求往內,回應反向往外離開的結構
請求往內,回應反向往外離開的結構
在貼上程式碼前,我先在紙上畫出分層結構
在貼上程式碼前,我先在紙上畫出分層結構

這樣設計有什麼好處?

整理一下我套用到實際專案後感受到的優點。

  1. 新增功能變成在陣列中加入一行。 需要快取時,只要把CachingMiddleware()插入陣列即可。

  2. **順序很容易調整。**要讓驗證先於記錄還是後於記錄執行,只要改變陣列順序。

  3. **測試更方便。**每個中介軟體都彼此獨立,可以逐一加入單元測試。

  4. **核心變乾淨了。**原本 200 行的send()又回到十行以內。

相對地,也有需要注意的地方。

中介軟體太多時,很難追蹤一個請求究竟會經過哪些地方。

所以當鏈超過五、六個時,我會把記錄中介軟體放在最前面,記下通過路徑。


總結

用 Decorator 建立功能,再用 Chain of Responsibility 排定順序的組合,熟悉後不只適用於網路層,也能直接用在事件處理、驗證等場景。

建議把今天的範例接到一個小型玩具專案中。親手剝開每一層,概念會更快內化。祝你重構愉快!

延伸閱讀