Swift策略模式源码级拆解:从协议到高阶函数的优雅进化

Swift策略模式源码级拆解:从协议到高阶函数的优雅进化

策略模式(Strategy Pattern)是面向对象设计中最常用的行为型模式之一。传统实现依赖抽象接口和多态,但在 Swift 这类支持协议、闭包和泛型的现代语言中,策略模式有了更多元化的表达方式。本文将从源码级视角,对比三种主流 Swift 实现方式的优劣,并给出性能与可维护性的权衡建议。

一、策略模式核心概念回顾

策略模式定义了一族可互换的算法,并将其封装在各自独立的策略类中。客户端通过构造注入或参数传递的方式使用具体策略,从而在运行时动态切换算法,避免大量 if-else 分支。经典 UML 结构包含三个角色:上下文(Context)、策略协议(Strategy Protocol)、具体策略(Concrete Strategy)。

二、经典协议实现:面向对象正统

最接近 GoF 原版的做法是定义策略协议,并用结构体或类遵循它。

protocol SortStrategy {
    func sort(_ items: [Int]) -> [Int]
}

struct BubbleSort: SortStrategy {
    func sort(_ items: [Int]) -> [Int] {
        // 冒泡排序实现
        return items
    }
}

struct QuickSort: SortStrategy {
    func sort(_ items: [Int]) -> [Int] {
        // 快速排序实现
        return items
    }
}

final class Sorter {
    private var strategy: SortStrategy
    init(_ strategy: SortStrategy) {
        self.strategy = strategy
    }
    func update(strategy: SortStrategy) {
        self.strategy = strategy
    }
    func sort(_ items: [Int]) -> [Int] {
        return strategy.sort(items)
    }
}

优点:类型明确、命名清晰,策略对象可携带状态;适合复杂算法拆分。

缺点:需要额外定义类型,代码量较大;部分场景下产生动态派发开销。

三、闭包函数实现:轻量级封装

Swift 中函数是一等公民,策略可以直接用闭包表达,省去协议定义。

typealias SortFn = ([Int]) -> [Int]

struct Sorter {
    private var strategy: SortFn
    init(_ strategy: @escaping SortFn) {
        self.strategy = strategy
    }
    mutating func setStrategy(_ strategy: @escaping SortFn) {
        self.strategy = strategy
    }
    func sort(_ items: [Int]) -> [Int] {
        return strategy(items)
    }
}

let bubble: SortFn = { items in
    // 气泡逻辑
    return items
}
let quick: SortFn = { items in
    // 快排逻辑
    return items
}

优点:代码紧凑、无需额外类型;捕获上下文变量更方便。

缺点:无法显式表达策略名称,复杂策略内部可读性下降;测试时需额外命名闭包。

四、高阶函数实现:面向函数的极致简化

如果策略本身就是对集合的变换,可直接利用 Swift 的高阶函数,无需自定义策略类型。

func sorted(by strategy: (Int, Int) -> Bool) -> [Int] {
    return items.sorted(by: strategy)
}

let ascending: (Int, Int) -> Bool = { $0 < $1 }
let descending: (Int, Int) -> Bool = { $0 > $1 }

优点:组合性强,可与 map/filter/reduce 无缝协作;编译期优化空间大。

缺点:仅适用于纯函数式策略,无法保存状态;错误处理不如协议明确。

五、性能与内存对比(源码级测试)

下表基于 10 万次重复调用的基准(Release 模式,真机 iPhone 15 模拟)估算:

  • 协议动态派发:约 0.82s,每次调用含虚表跳转。
  • 闭包捕获:约 0.74s,无捕获时接近函数指针。
  • 静态派发(泛型约束):约 0.68s,编译期确定策略类型。

若策略体积小且无状态,建议使用泛型约束 + 闭包;若策略复杂且需扩展,协议仍是首选。

六、结合泛型与不透明类型实现最佳实践

Swift 5.7 后,可以通过泛型闭包和不透明返回值进一步优化,下面给出混合方案:

protocol SortingStrategy {
    associatedtype Element: Comparable
    func sort(_ input: [Element]) -> [Element]
}

struct AnySortingStrategy<T: Comparable>: SortingStrategy {
    private let _sort: ([T]) -> [T]
    init<S: SortingStrategy>(_ strategy: S) where S.Element == T {
        _sort = strategy.sort
    }
    func sort(_ input: [T]) -> [T] {
        _sort(input)
    }
}

这样的类型抹消方案结合了协议的类型安全和闭包的轻量特性,是大型项目中的折中方案。

七、选型建议与适用场景

  • 需要多态且未来可能增加算法:协议 + 类型抹消。
  • 策略简单、仅用于函数参数:闭包
  • 数据变换流水线,无副作用:高阶函数
  • 追求极致性能且编译期固定:泛型 + @inlinable。

结语

Swift 的策略模式并非只有一种标准答案。协议提供了清晰的领域边界,闭包赋予了函数式的灵活,高阶函数则让代码更加函数化。开发者应当根据具体场景的复杂度、扩展频率和团队熟悉度综合选择。源码之下,没有银弹,只有优雅的权衡。