4-4.【协议导向编程】如何使用协议和泛型实现高度解耦的可复用组件?请举例。

5 阅读2分钟

👉 如何用「协议 + 泛型」做一个高度解耦、可复用、可测试的组件

我会按这个顺序来:

  1. 设计目标(为什么要协议 + 泛型)
  2. 核心思想(解耦点在哪)
  3. 完整示例:网络请求组件
  4. 为什么这套设计“真的好用”
  5. 可迁移到其他场景的通用模板

一、设计目标(现实问题)

假设你在做一个 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 组件