👉 如何用「协议 + 泛型」做一个高度解耦、可复用、可测试的组件
我会按这个顺序来:
- 设计目标(为什么要协议 + 泛型)
- 核心思想(解耦点在哪)
- 完整示例:网络请求组件
- 为什么这套设计“真的好用”
- 可迁移到其他场景的通用模板
一、设计目标(现实问题)
假设你在做一个 App,需要:
- 网络层可以替换(URLSession / Mock / 本地 JSON)
- 解码方式可替换(JSON / Protobuf)
- 业务层不关心具体实现
- 能方便写单元测试
❌ 常见反例(强耦合)
class APIClient {
func fetchUser(completion: (User) -> Void) {
URLSession.shared.dataTask(...) { data, _, _ in
let user = try! JSONDecoder().decode(User.self, from: data!)
completion(user)
}
}
}
问题:
- 强依赖
URLSession - 强依赖
JSONDecoder - 无法 mock
- 复用性几乎为 0
二、核心思想(解耦设计原则)
每一个变化点 = 一个协议
| 变化点 | 抽象方式 |
|---|---|
| 网络请求方式 | 协议 |
| 数据解码方式 | 协议 |
| 数据模型 | 泛型 |
三、完整示例:协议 + 泛型的网络组件
1️⃣ 抽象网络行为(协议)
protocol NetworkSession {
func request(
_ request: URLRequest,
completion: @escaping (Result<Data, Error>) -> Void
)
}
默认实现(真实网络)
final class URLSessionAdapter: NetworkSession {
func request(
_ request: URLRequest,
completion: @escaping (Result<Data, Error>) -> Void
) {
URLSession.shared.dataTask(with: request) { data, _, error in
if let data = data {
completion(.success(data))
} else {
completion(.failure(error ?? URLError(.badServerResponse)))
}
}.resume()
}
}
Mock 实现(测试)
struct MockSession: NetworkSession {
let data: Data
func request(
_ request: URLRequest,
completion: @escaping (Result<Data, Error>) -> Void
) {
completion(.success(data))
}
}
👉 业务层完全不知道它用的是哪种 session
2️⃣ 抽象解码行为(协议 + 泛型)
protocol DataDecoder {
func decode<T: Decodable>(_ data: Data) throws -> T
}
JSON 解码器实现
struct JSONDataDecoder: DataDecoder {
func decode<T: Decodable>(_ data: Data) throws -> T {
try JSONDecoder().decode(T.self, from: data)
}
}
3️⃣ 组合组件(泛型驱动)
final class APIClient {
private let session: NetworkSession
private let decoder: DataDecoder
init(
session: NetworkSession,
decoder: DataDecoder
) {
self.session = session
self.decoder = decoder
}
func fetch<T: Decodable>(
request: URLRequest,
completion: @escaping (Result<T, Error>) -> Void
) {
session.request(request) { result in
switch result {
case .success(let data):
do {
let model: T = try self.decoder.decode(data)
completion(.success(model))
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
4️⃣ 使用方式(解耦后的好处)
正式环境
let client = APIClient(
session: URLSessionAdapter(),
decoder: JSONDataDecoder()
)
client.fetch(request: userRequest) { (result: Result<User, Error>) in
print(result)
}
单元测试
let mockData = """
{ "id": 1, "name": "Alice" }
""".data(using: .utf8)!
let client = APIClient(
session: MockSession(data: mockData),
decoder: JSONDataDecoder()
)
client.fetch(request: dummyRequest) { (result: Result<User, Error>) in
// 可预测、无网络
}
四、为什么「协议 + 泛型」特别强?
✅ 1. 依赖倒置(DIP)
高层模块(APIClient)
⬇️ 只依赖协议
⬆️ 具体实现向上注入
✅ 2. 泛型让组件“类型安全 + 高复用”
func fetch<T: Decodable>(...) -> T
- 同一套代码
- 适用于任意模型
- 编译期类型检查
✅ 3. 协议 = 行为边界,泛型 = 类型自由
协议负责「能做什么」
泛型负责「作用于谁」
这是 Swift 架构里非常黄金的组合。
五、可迁移到任何模块的通用模板
你可以把这个模式套到:
- 缓存系统(Cacheable + 泛型 Value)
- 数据存储(Storage + 泛型 Entity)
- 日志系统(Logger + Strategy)
- SwiftUI ViewModel(Service 协议注入)
- 支付 / 权限 / 定位 / 推送模块
六、一句话总结(工程级)
协议用来“切断依赖”,泛型用来“消除重复”
两者结合 = 高度解耦、强复用、易测试的 Swift 组件