示例代码均为匿名化改写,用于说明模式;网络实现和响应模型由应用提供。
先给外部数据源留一条替换路径
目录数据页面需要读取分类、分页项目和搜索结果。可以先定义一个小型异步协议描述这些能力,再由线上实现调用统一网络客户端并解码业务模型。视图接收协议,不直接创建 URLSession 或拼接服务器请求。
应用组装层负责创建具体实现并把它传给页面。这种接线能让页面在测试中使用内存替身,控制加载顺序、空结果和失败状态,而不必依赖真实后端。
struct CatalogItem: Identifiable, Sendable {
let id: UUID
let title: String
}
protocol CatalogProviding: Sendable {
func items(page: Int) async throws -> [CatalogItem]
}
@MainActor
final class CatalogModel {
private let provider: any CatalogProviding
private(set) var items: [CatalogItem] = []
init(provider: any CatalogProviding) {
self.provider = provider
}
func loadFirstPage() async throws {
items = try await provider.items(page: 1)
}
}
依赖注入不必一次铺满
已有工程可能一部分功能使用协议注入,另一部分仍从共享实例调用。优先为外部副作用多、状态复杂、测试成本高的边界增加替换能力即可;纯模型并不需要为了形式统一而包一层协议。
协议是源码层的边界。如果工程仍在一个应用 target 中编译,目录分层不会自动阻止跨功能引用。是否提取独立模块,应根据稳定性、复用需求和维护成本决定。
协议优先用于网络、系统副作用和需要控制时序的边界;普通模型不必为了形式统一再包一层。若某条热路径确实受动态派发影响,再用性能测量决定是否改成泛型约束。
数据合同要留在适配层
后端字段映射、分页默认值和错误 envelope 属于服务实现的职责。它们应集中在适配层,并有针对参数和模型解码的检查。视图不应知道服务端字段名,也不应自行处理多种响应格式。
