面向协议编程

探究 Swift 的灵魂设计哲学:利用协议扩展(POP)与泛型约束,构建高度解耦的模块化系统。

Swift 的灵魂:组合优于继承。POP 是 Swift 区别于其他语言的核心范式,让代码更灵活、可测试、可复用。


1. 为什么选择 POP

OOP 的局限性

// 传统继承的问题
class Animal {
    func eat() { }
}

class Bird: Animal {
    func fly() { }
}

class Penguin: Bird {
    // 企鹅不会飞,但继承了 fly() 方法
    // 只能 override 抛出错误,设计上不合理
    override func fly() {
        fatalError("企鹅不会飞!")
    }
}

POP 的解决方案

protocol Eatable {
    func eat()
}

protocol Flyable {
    func fly()
}

struct Sparrow: Eatable, Flyable {
    func eat() { print("吃虫子") }
    func fly() { print("飞翔") }
}

struct Penguin: Eatable {
    func eat() { print("吃鱼") }
    // 不需要实现 fly()
}

对比

特性 OOP (继承) POP (协议)
复用方式 垂直继承 水平组合
灵活性 单继承限制 多协议遵循
值类型支持 仅 Class Struct/Enum/Class
测试性 需要 Mock 子类 协议 Mock 简单

2. 协议扩展 (Protocol Extensions)

协议扩展是 POP 的核心,可以为协议提供默认实现。

默认实现

protocol Describable {
    var name: String { get }
    func describe() -> String
}

extension Describable {
    // 默认实现
    func describe() -> String {
        return "这是 \(name)"
    }
}

struct Product: Describable {
    let name: String
    // 自动获得 describe() 方法
}

struct DetailedProduct: Describable {
    let name: String
    let price: Double
    
    // 可以覆盖默认实现
    func describe() -> String {
        return "\(name) - ¥\(price)"
    }
}

let p1 = Product(name: "iPhone")
print(p1.describe())  // 这是 iPhone

let p2 = DetailedProduct(name: "MacBook", price: 9999)
print(p2.describe())  // MacBook - ¥9999.0

条件扩展

// 仅当 Element 遵循 Numeric 时才添加 sum()
extension Collection where Element: Numeric {
    func sum() -> Element {
        reduce(0, +)
    }
}

let numbers = [1, 2, 3, 4, 5]
print(numbers.sum())  // 15

// 仅当 Element 遵循 Equatable 时
extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        guard let first = first else { return true }
        return allSatisfy { $0 == first }
    }
}

print([1, 1, 1].allEqual())  // true
print([1, 2, 1].allEqual())  // false

为标准库类型扩展

extension String {
    var isValidEmail: Bool {
        contains("@") && contains(".")
    }
}

extension Array where Element: Hashable {
    var unique: [Element] {
        Array(Set(self))
    }
}

"test@example.com".isValidEmail  // true
[1, 2, 2, 3, 3, 3].unique        // [1, 2, 3]

3. 协议组合

使用 & 组合多个协议。

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

// 组合协议
typealias Person = Named & Aged

struct Student: Person {
    let name: String
    let age: Int
}

// 函数参数使用组合协议
func greet(_ person: Named & Aged) {
    print("你好,\(person.name),你 \(person.age) 岁了")
}

greet(Student(name: "小明", age: 20))

4. 关联类型 (Associated Types)

让协议支持泛型。

基础用法

protocol Container {
    associatedtype Item
    
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}

struct Stack<Element>: Container {
    // 编译器自动推断 Item = Element
    private var items: [Element] = []
    
    var count: Int { items.count }
    
    mutating func append(_ item: Element) {
        items.append(item)
    }
    
    subscript(i: Int) -> Element {
        items[i]
    }
    
    mutating func pop() -> Element? {
        items.popLast()
    }
}

var stack = Stack<Int>()
stack.append(1)
stack.append(2)
print(stack[0])  // 1

约束关联类型

protocol ComparableContainer {
    associatedtype Item: Comparable
    
    var items: [Item] { get }
    func sorted() -> [Item]
}

extension ComparableContainer {
    func sorted() -> [Item] {
        items.sorted()
    }
}

5. 泛型约束

where 子句

// 要求两个容器的 Item 类型相同且可比较
func compareContainers<C1: Container, C2: Container>(
    _ c1: C1,
    _ c2: C2
) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
    guard c1.count == c2.count else { return false }
    
    for i in 0..<c1.count {
        if c1[i] != c2[i] {
            return false
        }
    }
    return true
}

some 和 any

// some - 不透明类型(Swift 5.1+)
func makeCollection() -> some Collection {
    [1, 2, 3]
}

// any - 存在类型(Swift 5.6+)
func processCollections(_ collections: [any Collection]) {
    for collection in collections {
        print(collection.count)
    }
}

6. 实战案例

网络层抽象

protocol APIClient {
    func fetch<T: Decodable>(_ endpoint: String) async throws -> T
}

// 真实实现
struct URLSessionClient: APIClient {
    func fetch<T: Decodable>(_ endpoint: String) async throws -> T {
        let url = URL(string: "https://api.example.com/\(endpoint)")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return try JSONDecoder().decode(T.self, from: data)
    }
}

// Mock 实现用于测试
struct MockClient: APIClient {
    var mockData: Data
    
    func fetch<T: Decodable>(_ endpoint: String) async throws -> T {
        try JSONDecoder().decode(T.self, from: mockData)
    }
}

// ViewModel 依赖协议而非具体实现
class UserViewModel {
    private let client: APIClient
    
    init(client: APIClient = URLSessionClient()) {
        self.client = client
    }
    
    func loadUser() async throws -> User {
        try await client.fetch("user")
    }
}

可测试的依赖注入

protocol StorageService {
    func save(_ data: Data, key: String)
    func load(key: String) -> Data?
}

struct UserDefaultsStorage: StorageService {
    func save(_ data: Data, key: String) {
        UserDefaults.standard.set(data, forKey: key)
    }
    
    func load(key: String) -> Data? {
        UserDefaults.standard.data(forKey: key)
    }
}

struct InMemoryStorage: StorageService {
    private var storage: [String: Data] = [:]
    
    mutating func save(_ data: Data, key: String) {
        storage[key] = data
    }
    
    func load(key: String) -> Data? {
        storage[key]
    }
}

策略模式

protocol PaymentStrategy {
    func pay(amount: Double) -> Bool
}

struct CreditCardPayment: PaymentStrategy {
    let cardNumber: String
    
    func pay(amount: Double) -> Bool {
        print("信用卡支付 ¥\(amount)")
        return true
    }
}

struct ApplePayPayment: PaymentStrategy {
    func pay(amount: Double) -> Bool {
        print("Apple Pay 支付 ¥\(amount)")
        return true
    }
}

class ShoppingCart {
    var paymentStrategy: PaymentStrategy
    
    init(paymentStrategy: PaymentStrategy) {
        self.paymentStrategy = paymentStrategy
    }
    
    func checkout(amount: Double) -> Bool {
        paymentStrategy.pay(amount: amount)
    }
}

7. 协议见证表 (Protocol Witness Table)

Swift 使用 Witness Table 实现协议的动态派发。

┌─────────────────────────────────┐
│     Protocol Witness Table      │
├─────────────────────────────────┤
│  method1 → 具体实现地址          │
│  method2 → 具体实现地址          │
│  property → getter/setter 地址  │
└─────────────────────────────────┘

性能考虑

// 使用协议作为类型(存在类型)有性能开销
func process(_ items: [Drawable]) {  // 动态派发
    for item in items {
        item.draw()
    }
}

// 使用泛型约束可以静态派发
func process<T: Drawable>(_ items: [T]) {  // 静态派发
    for item in items {
        item.draw()
    }
}

8. 最佳实践

✅ 推荐

// 1. 优先使用协议组合而非继承
protocol Identifiable { var id: UUID { get } }
protocol Timestamped { var createdAt: Date { get } }

struct Post: Identifiable, Timestamped {
    let id = UUID()
    let createdAt = Date()
}

// 2. 为协议提供默认实现
extension Identifiable {
    var id: UUID { UUID() }
}

// 3. 使用协议进行依赖注入
class ViewModel {
    private let service: DataService  // 协议类型
    init(service: DataService) { self.service = service }
}

// 4. 使用 typealias 简化复杂协议组合
typealias DataModel = Identifiable & Codable & Hashable

❌ 避免

// 1. 不要过度使用协议
// ❌ 每个类都定义一个协议
protocol UserServiceProtocol { }
class UserService: UserServiceProtocol { }  // 只有一个实现

// 2. 不要在协议中放太多要求
// ❌ 巨型协议
protocol Everything {
    func a(); func b(); func c(); func d()
    // ...20个方法
}

// ✅ 拆分成小协议
protocol A { func a() }
protocol B { func b() }

// 3. 避免协议扩展中的状态
extension MyProtocol {
    // ❌ 扩展中不能存储属性
    var cache: [String] { [:] }  // 每次都是新实例
}