构建网络层时,你一定会遇到这样的时刻。
发送一个请求,既要附加认证令牌、记录日志,失败时还要重试。
把这些全塞进一个函数后,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 排列顺序的组合,熟悉之后不仅适用于网络层,也能直接用于事件处理、校验等场景。
建议把今天的示例接入一个小型玩具项目。亲手剥开这些层次,概念会更快掌握。祝你重构愉快!

