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] { [:] } // 每次都是新实例
}