首页 / Swift 编程语言教程 / 协议扩展与面向协议编程

Swift 编程语言教程

协议扩展与面向协议编程

本教程共 93 篇 · 第 68 篇 · 更新于 2026-08-08 · 约 9 分钟阅读

SwiftSwift 编程语言教程protocol extension协议扩展默认实现面向协议编程POPwhere 约束

本节目标:学会用协议扩展给协议要求提供默认实现,理解”面向协议编程”为什么能替代一部分继承的职责。

上一章讲的是协议只定义”要求”,具体实现交给遵循者。但有些行为其实所有遵循者都该一样,没必要让每个类型各写一遍。Swift 的解法是:给协议本身写扩展,在扩展里把方法、计算属性、初始化器、下标直接实现出来。这样所有遵循者自动白拿到这些功能。

1-1 给协议加”自带行为”

extension 给协议加实现,遵循者无需任何额外代码就获得了这些方法。下面给 RandomNumberGenerator 加一个 randomBool()

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

let generator = LinearCongruentialGenerator()
print(generator.randomBool())

LinearCongruentialGenerator 自己一行没加,但因为遵循了协议、协议扩展又提供了实现,它直接就能调 randomBool()。这就是协议扩展的核心价值:行为写在协议这一层,而不是散落在每个类型里。

Note

协议扩展能给遵循者加实现,但不能让一个协议去”继承”另一个协议——协议继承只能在协议声明里用冒号写。扩展只负责”补实现”,不负责”加要求”。

1-2 默认实现:要求也能有现成答案

协议扩展还能给”协议要求的方法或计算属性”提供默认实现。如果某个遵循者自己写了实现,就用它自己的;否则用扩展里的默认版。

extension PrettyTextRepresentable {
    var prettyTextualDescription: String {
        return textualDescription
    }
}

这里 PrettyTextRepresentable 继承 TextRepresentable,它要求一个 prettyTextualDescription。扩展给了个默认实现:直接返回 textualDescription。遵循者若觉得”够用了”,就可以不重写,白白省一段代码。

要注意它和”可选协议要求”不是一回事。可选要求(仅 @objc 协议才有)调用时要用可选链 ?,而带默认实现的要求可以像普通成员一样直接调用,不需要问号。

1-3 给协议扩展加约束

扩展协议时还能加条件:只有当遵循者满足某些约束时,扩展里的方法才可用。约束写在协议名后面,用泛型 where 子句表达。

extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}

这个扩展给标准库的 Collection 协议加了 allEqual(),但只对”元素遵循 Equatable”的集合生效。因为要比较元素是否相等,所以必须约束元素可比较。不满足条件的集合,调用这个方法会编译报错。

let equalNumbers = [100, 100, 100]
let differentNumbers = [100, 100, 200]
print(equalNumbers.allEqual())      // true
print(differentNumbers.allEqual())  // false

如果同一协议有多条带约束的扩展都提供了同名方法,Swift 会用”约束最具体”的那条。这条规则让你能把不同场景的行为拆成多条扩展,互不干扰。

1-4 面向协议编程的思想

所谓”面向协议编程”(Protocol-Oriented Programming,常缩写为 POP),是把协议当作组织代码的核心工具。传统面向对象里,复用行为主要靠类继承;但 Swift 的值类型(结构体、枚举)不能继承,而它们又无处不在。协议扩展正好补上了这块短板:你定义一个协议描述能力,再用协议扩展把通用实现放进去,然后让结构体、枚举、类都来遵循——三者都能平等地获得这份能力。

它的好处很实在。第一,值类型也能复用行为,不必为了共享功能而被迫改成类。第二,一个类型可以同时遵循多个协议,相当于”多重能力叠加”,比单继承灵活。第三,默认实现减少了样板代码,新类型只要补齐真正不同的部分即可。第四,协议作为类型时,调用方只依赖”能力”而不依赖”具体类型”,耦合更低、更易替换。

Tip

经验法则:当你想给一批类型共享行为,先想”它们共同的能力是什么”,把它提炼成协议 + 协议扩展;而不是急着建一个基类让大家都去继承。尤其在 Swift 里,这往往更自然。

1-5 一个组合多个协议的小例子

协议组合能把几份小协议拼成”一次性要求”。比如既要能命名、又要能比较年龄:

protocol Named {
    var name: String { get }
}
protocol ComparableByAge {
    var age: Int { get }
}
struct User: Named, ComparableByAge {
    var name: String
    var age: Int
}
func older(_ a: Named & ComparableByAge, _ b: Named & ComparableByAge) -> String {
    return a.age >= b.age ? a.name : b.name
}

NamedComparableByAge 各自只管一小块能力,谁需要就遵循谁。函数参数用 Named & ComparableByAge 表示”同时具备两者”。这种”小块能力自由拼接”的风格,正是面向协议编程推崇的组织方式。

1-6 协议扩展与条件遵循

泛型类型有时只有在其内部类型满足某协议时,才能去遵循另一个协议。这时用”条件遵循”:在扩展遵循协议时再追加 where 约束。标准库里 Array 就是这样——只有当元素遵循 TextRepresentable 时,[Element] 才遵循它:

extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}

这说明协议扩展不仅能给已有类型补功能,还能让泛型类型”有条件地”去遵循协议,把能力精确地控制在成立的范围内。

1-7 协议扩展不能做的事

讲完能力,也要认清边界。协议扩展不能让一个协议去继承另一个协议——继承只能写在协议声明本身的冒号后面。扩展也不能给协议加”存储属性”,它只能提供计算属性与方法。更重要的是:如果多个来源(比如类型自身、多个扩展)都提供了同一要求的实现,Swift 有一套明确的优先级来选”用谁的”,通常”离类型更近、约束更具体”的胜出。理解这点能避免”我以为会调这个,实际调了那个”的困惑——当你怀疑默认实现没按预期生效,先用最具体的约束、再检查类型自身有没有自己的实现。

1-8 小结

协议扩展让协议不只是”要求清单”,还能自带实现,所有遵循者自动受益。给协议要求提供默认实现,能省掉重复代码;给扩展加 where 约束,则能把功能精确限定在满足条件的类型上。面向协议编程正是围绕这两点展开:用协议描述能力、用协议扩展提供通用实现,让结构体、枚举、类都能平等地复用行为,而不必依赖类继承。下章我们把视角转到协议里更进阶的”关联类型”。