软件设计

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 排列顺序的组合,熟悉之后不仅适用于网络层,也能直接用于事件处理、校验等场景。

建议把今天的示例接入一个小型玩具项目。亲手剥开这些层次,概念会更快掌握。祝你重构愉快!

延伸阅读