Swift模板方法:算法骨架的定制化

Swift模板方法:算法骨架的定制化

引言:你还在重复编写相似算法吗?

在Swift开发中,你是否曾遇到这样的困境:多个类需要实现相似的算法流程,但部分步骤的具体实现又有所不同?例如,文件解析器需要统一的"打开-解析-关闭"流程,但不同格式的文件解析逻辑截然不同;数据转换器需要"验证-转换-输出"的固定步骤,但验证规则和转换逻辑千差万别。如果你仍在为每个场景重复编写完整算法,不仅导致代码冗余,更会让后期维护变成一场噩梦。

本文将深入探讨模板方法模式(Template Method Pattern)——这一专为解决"算法骨架固定、步骤实现可变"问题而生的设计模式。通过本文,你将掌握:

  • 模板方法模式的核心架构与Swift实现技巧
  • 如何利用协议扩展与抽象类构建灵活的算法骨架
  • 标准库中的模板方法应用案例深度解析
  • 3种实战场景(网络请求、数据解析、UI渲染)的完整实现
  • 性能优化与内存管理的关键注意事项

模板方法模式:架构解析与Swift实现

核心定义与UML类图

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义一个算法的骨架,并允许子类(或遵循协议的类型)为一个或多个步骤提供具体实现。这种模式将算法的不变部分封装在父类(或协议扩展)中,而将可变部分延迟到子类(或具体类型)实现,从而实现代码复用与灵活扩展的平衡。

mermaid

Swift实现方式对比

在Swift中实现模板方法模式主要有两种方式,每种方式各有适用场景:

1. 基于协议扩展的实现(推荐)
// 定义算法步骤协议
protocol DataProcessor {
    // 原语操作:必须实现的步骤
    func validateData(_ data: Data) throws
    func parseData(_ data: Data) throws -> [String: Any]
    func saveParsedData(_ parsedData: [String: Any]) throws
    
    // 钩子方法:可选实现的步骤
    func willStartProcessing()
    func didFinishProcessing(success: Bool)
    
    // 模板方法:定义算法骨架
    func processData(_ data: Data)
}

// 协议扩展实现模板方法和默认钩子
extension DataProcessor {
    func processData(_ data: Data) {
        // 1. 预处理钩子
        willStartProcessing()
        var success = false
        
        defer {
            // 5. 后处理钩子
            didFinishProcessing(success: success)
        }
        
        do {
            // 2. 验证数据(必须实现)
            try validateData(data)
            // 3. 解析数据(必须实现)
            let parsedData = try parseData(data)
            // 4. 保存数据(必须实现)
            try saveParsedData(parsedData)
            success = true
        } catch {
            print("处理失败:\(error)")
        }
    }
    
    // 默认钩子实现
    func willStartProcessing() {}
    func didFinishProcessing(success: Bool) {}
}

// JSON数据处理器(具体实现)
class JSONDataProcessor: DataProcessor {
    func validateData(_ data: Data) throws {
        guard !data.isEmpty else {
            throw DataError.emptyData
        }
        // JSON数据特定验证逻辑
    }
    
    func parseData(_ data: Data) throws -> [String: Any] {
        guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            throw DataError.invalidFormat
        }
        return json
    }
    
    func saveParsedData(_ parsedData: [String: Any]) throws {
        // JSON数据存储逻辑
        print("保存JSON数据:\(parsedData)")
    }
    
    // 重写钩子方法
    func willStartProcessing() {
        print("JSON数据处理器开始工作...")
    }
}

// 错误类型定义
enum DataError: Error {
    case emptyData
    case invalidFormat
    case storageFailed
}
2. 基于抽象类的实现(适用于复杂继承场景)
// 抽象基类(使用final模板方法+必须重写的方法)
class NetworkRequestHandler {
    // 模板方法:标记为final防止子类修改算法骨架
    final func performRequest(url: URL) async throws -> Data {
        // 1. 准备请求
        let request = try prepareRequest(url: url)
        // 2. 发送请求
        let (data, response) = try await URLSession.shared.data(for: request)
        // 3. 验证响应
        try validateResponse(response, data: data)
        // 4. 处理响应数据
        let processedData = processResponseData(data)
        return processedData
    }
    
    // 必须重写的原语操作
    func prepareRequest(url: URL) throws -> URLRequest {
        fatalError("子类必须实现prepareRequest方法")
    }
    
    // 必须重写的原语操作
    func validateResponse(_ response: URLResponse, data: Data) throws {
        fatalError("子类必须实现validateResponse方法")
    }
    
    // 钩子方法:提供默认实现,子类可选择性重写
    func processResponseData(_ data: Data) -> Data {
        return data // 默认不处理
    }
}

// 具体实现类:JSON API请求处理器
class JSONAPIRequestHandler: NetworkRequestHandler {
    override func prepareRequest(url: URL) throws -> URLRequest {
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        return request
    }
    
    override func validateResponse(_ response: URLResponse, data: Data) throws {
        guard let httpResponse = response as? HTTPURLResponse else {
            throw NetworkError.invalidResponse
        }
        
        guard 200..<300 ~= httpResponse.statusCode else {
            if let errorData = try? JSONSerialization.jsonObject(with: data) {
                throw NetworkError.httpError(code: httpResponse.statusCode, details: errorData)
            } else {
                throw NetworkError.httpError(code: httpResponse.statusCode, details: nil)
            }
        }
    }
    
    // 重写钩子方法处理数据
    override func processResponseData(_ data: Data) -> Data {
        // 移除JSON数据中的BOM头(如果存在)
        if data.starts(with: Data([0xEF, 0xBB, 0xBF])) {
            return data.dropFirst(3).data
        }
        return data
    }
}

// 网络错误类型
enum NetworkError: Error {
    case invalidResponse
    case httpError(code: Int, details: Any?)
    case requestFailed
}

extension Data.SubSequence {
    var data: Data { Data(self) }
}

两种实现方式的对比分析

实现方式优势劣势适用场景
协议扩展• 符合Swift值类型优先原则
• 支持多协议组合
• 编译期静态派发,性能更好
• 避免继承的局限性
• 无法存储状态
• 需要显式声明所有协议方法
• 算法步骤较少(<5个)
• 不需要共享状态
• 优先使用值类型(struct/enum)
抽象类• 可以存储共享状态
• 支持动态派发
• 适合复杂继承体系
• 可提供部分实现
• 必须使用引用类型(class)
• 单继承限制
• 动态派发可能带来性能损耗
• 算法步骤较多(>5个)
• 需要共享状态
• 复杂的继承关系

最佳实践:在Swift中优先使用协议扩展+值类型的实现方式,这更符合Swift语言的设计哲学。只有当需要共享状态或实现复杂的继承关系时,才考虑使用抽象基类。

Swift标准库中的模板方法模式

Swift标准库广泛应用了模板方法模式,理解这些实现可以帮助我们更好地设计自己的代码。

Sequence协议:迭代算法的模板实现

Sequence协议是模板方法模式在Swift标准库中的经典应用。它定义了迭代算法的骨架,同时允许具体序列类型自定义迭代行为:

// 简化版Sequence协议定义
protocol Sequence {
    associatedtype Element
    associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
    
    // 原语操作:必须实现
    func makeIterator() -> Iterator
    
    // 模板方法:协议扩展中实现
    var underestimatedCount: Int { get }
    func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
    func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]
    // ...其他算法方法
}

// 协议扩展实现模板方法
extension Sequence {
    // map方法的模板实现
    func map<T>(_ transform: (Element) throws -> T) rethrows -> [T] {
        let initialCapacity = underestimatedCount
        var result = ContiguousArray<T>()
        result.reserveCapacity(initialCapacity)
        
        var iterator = makeIterator()
        // 添加元素到初始容量
        for _ in 0..<initialCapacity {
            result.append(try transform(iterator.next()!))
        }
        // 添加剩余元素
        while let element = iterator.next() {
            result.append(try transform(element))
        }
        return Array(result)
    }
    
    // filter方法的模板实现
    func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element] {
        var result = ContiguousArray<Element>()
        var iterator = makeIterator()
        
        while let element = iterator.next() {
            if try isIncluded(element) {
                result.append(element)
            }
        }
        return Array(result)
    }
}

在这个实现中:

  • makeIterator()原语操作,由具体序列类型(如ArraySetDictionary)实现
  • mapfilterforEach等方法是模板方法,在协议扩展中提供默认实现
  • 具体序列类型可以通过重写这些方法来提供更高效的实现(如Arraymap的优化实现)

Collection协议:索引算法的模板设计

Collection协议在Sequence基础上进一步扩展,增加了对索引的支持,其模板方法设计更加复杂:

mermaid

Collection协议定义了遍历算法的骨架,具体实现类只需提供startIndexendIndexindex(after:)等原语操作,即可自动获得for-in循环、prefixsuffix等功能。

实战场景:从理论到实践

场景一:网络请求框架(抽象类实现)

实现一个通用的网络请求框架,支持不同的API类型(JSON API、XML API、Protobuf API):

// 抽象网络请求处理器
class APIRequestHandler {
    // 模板方法:定义请求流程
    final func sendRequest<T: Decodable>(completion: @escaping (Result<T, APIError>) -> Void) {
        Task {
            do {
                // 1. 创建请求(原语操作)
                let request = try buildRequest()
                // 2. 发送请求
                let (data, response) = try await URLSession.shared.data(for: request)
                // 3. 验证响应(原语操作)
                try validateResponse(response, data: data)
                // 4. 解析数据(钩子方法)
                let parsedData = try parseResponse(data)
                // 5. 映射为模型对象
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                let result = try decoder.decode(T.self, from: parsedData)
                // 6. 调用完成回调
                completion(.success(result))
            } catch {
                completion(.failure(error as? APIError ?? .unknown))
            }
        }
    }
    
    // 原语操作:构建请求(必须重写)
    func buildRequest() throws -> URLRequest {
        fatalError("子类必须实现buildRequest()")
    }
    
    // 原语操作:验证响应(必须重写)
    func validateResponse(_ response: URLResponse, data: Data) throws {
        fatalError("子类必须实现validateResponse()")
    }
    
    // 钩子方法:解析响应数据(可重写)
    func parseResponse(_ data: Data) throws -> Data {
        return data // 默认不处理
    }
}

// JSON API请求处理器
class JSONAPIHandler: APIRequestHandler {
    let endpoint: URL
    let method: HTTPMethod
    let parameters: [String: Any]?
    
    init(endpoint: URL, method: HTTPMethod = .get, parameters: [String: Any]? = nil) {
        self.endpoint = endpoint
        self.method = method
        self.parameters = parameters
    }
    
    override func buildRequest() throws -> URLRequest {
        var request = URLRequest(url: endpoint)
        request.httpMethod = method.rawValue
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        if let parameters = parameters, method != .get {
            request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
        }
        
        return request
    }
    
    override func validateResponse(_ response: URLResponse, data: Data) throws {
        guard let httpResponse = response as? HTTPURLResponse else {
            throw APIError.invalidResponse
        }
        
        guard 200..<300 ~= httpResponse.statusCode else {
            if let error = try? JSONDecoder().decode(APIErrorResponse.self, from: data) {
                throw APIError.serverError(code: httpResponse.statusCode, message: error.message)
            } else {
                throw APIError.httpError(code: httpResponse.statusCode)
            }
        }
    }
}

// XML API请求处理器
class XMLAPIHandler: APIRequestHandler {
    // 实现XML特定的buildRequest和validateResponse
    // ...
}

// 错误类型定义
enum APIError: Error, Equatable {
    case invalidURL
    case invalidResponse
    case httpError(code: Int)
    case serverError(code: Int, message: String)
    case parsingError
    case unknown
    
    static func == (lhs: APIError, rhs: APIError) -> Bool {
        switch (lhs, rhs) {
        case (.invalidURL, .invalidURL), (.invalidResponse, .invalidResponse), (.parsingError, .parsingError), (.unknown, .unknown):
            return true
        case (.httpError(let lCode), .httpError(let rCode)):
            return lCode == rCode
        case (.serverError(let lCode, let lMsg), .serverError(let rCode, let rMsg)):
            return lCode == rCode && lMsg == rMsg
        default:
            return false
        }
    }
}

struct APIErrorResponse: Decodable {
    let message: String
}

enum HTTPMethod: String {
    case get = "GET"
    case post = "POST"
    case put = "PUT"
    case delete = "DELETE"
}

// 使用示例
let userEndpoint = URL(string: "https://api.example.com/users")!
let handler = JSONAPIHandler(endpoint: userEndpoint)
handler.sendRequest { (result: Result<[User], APIError>) in
    switch result {
    case .success(let users):
        print("获取用户列表成功:\(users.count)人")
    case .failure(let error):
        print("获取用户列表失败:\(error)")
    }
}

struct User: Decodable {
    let id: Int
    let name: String
    let email: String
}

场景二:数据解析器(协议扩展实现)

实现一个数据解析器,支持多种格式(JSON、CSV、PropertyList):

// 数据解析协议
protocol DataParser {
    // 原语操作:解析数据
    func parse(data: Data) throws -> [String: Any]
    
    // 模板方法:完整解析流程
    func completeParse(data: Data) throws -> [String: Any]
}

// 协议扩展实现模板方法
extension DataParser {
    func completeParse(data: Data) throws -> [String: Any] {
        // 1. 验证数据
        guard !data.isEmpty else {
            throw ParseError.emptyData
        }
        
        // 2. 解析数据(原语操作)
        let parsedData = try parse(data: data)
        
        // 3. 数据转换(钩子方法)
        let transformedData = transformData(parsedData)
        
        // 4. 验证解析结果
        guard !transformedData.isEmpty else {
            throw ParseError.emptyResult
        }
        
        return transformedData
    }
    
    // 钩子方法:数据转换(可选实现)
    func transformData(_ data: [String: Any]) -> [String: Any] {
        return data // 默认不转换
    }
}

// JSON解析器
struct JSONDataParser: DataParser {
    func parse(data: Data) throws -> [String: Any] {
        guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
            throw ParseError.invalidFormat
        }
        return json
    }
    
    // 重写钩子方法:转换日期格式
    func transformData(_ data: [String: Any]) -> [String: Any] {
        var transformed = data
        if let dateString = transformed["created_at"] as? String {
            transformed["created_at"] = DateFormatter.iso8601.date(from: dateString)
        }
        return transformed
    }
}

// CSV解析器
struct CSVDataParser: DataParser {
    func parse(data: Data) throws -> [String: Any] {
        guard let csvString = String(data: data, encoding: .utf8) else {
            throw ParseError.encodingError
        }
        
        let lines = csvString.components(separatedBy: .newlines).filter { !$0.isEmpty }
        guard !lines.isEmpty else {
            throw ParseError.invalidFormat
        }
        
        let headers = lines[0].components(separatedBy: ",")
        var result: [String: Any] = [:]
        
        for (index, header) in headers.enumerated() {
            var values: [String] = []
            for line in lines.dropFirst() {
                let components = line.components(separatedBy: ",")
                if index < components.count {
                    values.append(components[index])
                }
            }
            result[header] = values
        }
        
        return result
    }
}

// 属性列表解析器
struct PropertyListDataParser: DataParser {
    func parse(data: Data) throws -> [String: Any] {
        guard let plist = try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else {
            throw ParseError.invalidFormat
        }
        return plist
    }
}

// 错误类型
enum ParseError: Error {
    case emptyData
    case invalidFormat
    case encodingError
    case emptyResult
}

// 日期格式化器扩展
extension DateFormatter {
    static let iso8601: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
}

// 使用示例
let jsonData = """
{
    "name": "Swift Template Method",
    "version": 1.0,
    "created_at": "2023-07-01T12:00:00Z",
    "features": ["reusable", "flexible", "performant"]
}
""".data(using: .utf8)!

do {
    let jsonParser = JSONDataParser()
    let result = try jsonParser.completeParse(data: jsonData)
    print("JSON解析结果:\(result)")
    print("解析的日期:\(result["created_at"] as? Date ?? Date())")
} catch {
    print("JSON解析失败:\(error)")
}

场景三:UI渲染系统(协议组合实现)

实现一个灵活的UI渲染系统,支持不同的布局方式:

import UIKit

// 布局协议
protocol LayoutProtocol {
    func calculateLayout(bounds: CGRect) -> [CGRect]
}

// 视图渲染协议
protocol RenderProtocol {
    associatedtype Content
    var content: Content { get set }
    func render(in view: UIView)
}

// 复合协议:布局+渲染
protocol Component: LayoutProtocol, RenderProtocol {
    // 模板方法:完整渲染流程
    func display(in view: UIView)
}

// 协议扩展实现模板方法
extension Component {
    func display(in view: UIView) {
        // 1. 清除现有内容
        view.subviews.forEach { $0.removeFromSuperview() }
        
        // 2. 计算布局(原语操作)
        let frames = calculateLayout(bounds: view.bounds)
        
        // 3. 渲染内容(原语操作)
        render(in: view)
        
        // 4. 应用布局
        applyLayout(to: view, frames: frames)
    }
    
    // 钩子方法:应用布局
    func applyLayout(to view: UIView, frames: [CGRect]) {
        for (index, subview) in view.subviews.enumerated() {
            guard index < frames.count else { break }
            subview.frame = frames[index]
        }
    }
}

// 具体实现:文本组件
struct TextComponent: Component {
    var content: String
    var font: UIFont
    var textColor: UIColor
    
    func calculateLayout(bounds: CGRect) -> [CGRect] {
        // 计算文本框布局
        let textSize = content.size(withAttributes: [
            .font: font
        ])
        let textFrame = CGRect(
            x: 16,
            y: 16,
            width: bounds.width - 32,
            height: textSize.height
        )
        return [textFrame]
    }
    
    func render(in view: UIView) {
        let label = UILabel()
        label.text = content
        label.font = font
        label.textColor = textColor
        label.numberOfLines = 0
        view.addSubview(label)
    }
}

// 具体实现:图片文本组合组件
struct ImageTextComponent: Component {
    struct Content {
        let image: UIImage
        let text: String
    }
    
    var content: Content
    var spacing: CGFloat = 16
    
    func calculateLayout(bounds: CGRect) -> [CGRect] {
        // 计算图片和文本的布局
        let imageSize = CGSize(width: 44, height: 44)
        let imageFrame = CGRect(
            x: 16,
            y: 16,
            width: imageSize.width,
            height: imageSize.height
        )
        
        let textWidth = bounds.width - 16 - imageSize.width - spacing - 16
        let textSize = content.text.size(withAttributes: [
            .font: UIFont.systemFont(ofSize: 16)
        ])
        let textFrame = CGRect(
            x: imageFrame.maxX + spacing,
            y: 16 + (imageSize.height - textSize.height)/2,
            width: textWidth,
            height: textSize.height
        )
        
        return [imageFrame, textFrame]
    }
    
    func render(in view: UIView) {
        // 渲染图片
        let imageView = UIImageView(image: content.image)
        imageView.contentMode = .scaleAspectFit
        view.addSubview(imageView)
        
        // 渲染文本
        let label = UILabel()
        label.text = content.text
        label.font = UIFont.systemFont(ofSize: 16)
        view.addSubview(label)
    }
}

// 使用示例
let textComponent = TextComponent(
    content: "Swift模板方法模式实战",
    font: UIFont.boldSystemFont(ofSize: 20),
    textColor: .black
)

let imageTextContent = ImageTextComponent.Content(
    image: UIImage(systemName: "swift")!,
    text: "Swift是一种强大而直观的编程语言,适用于iOS、macOS、watchOS和tvOS应用开发。"
)
let imageTextComponent = ImageTextComponent(content: imageTextContent)

// 在视图控制器中使用
class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        
        // 显示组件
        imageTextComponent.display(in: view)
    }
}

性能优化与最佳实践

内存管理注意事项

  1. 避免循环引用:当使用闭包作为钩子方法时,确保使用[weak self]避免循环引用:
class DataProcessor {
    var completion: (() -> Void)?
    
    func process() {
        // 错误示例:可能导致循环引用
        completion = {
            self.cleanup()
        }
        
        // 正确示例:使用weak self
        completion = { [weak self] in
            self?.cleanup()
        }
    }
    
    func cleanup() {
        // 清理资源
    }
}
  1. 及时释放资源:在模板方法中使用defer确保资源正确释放:
func processFile(path: String) throws {
    let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))
    defer {
        fileHandle.closeFile()
    }
    
    // 使用fileHandle处理文件内容
    // ...
}

性能优化技巧

  1. 缓存计算结果:对于耗时的布局计算,缓存结果直到相关参数改变:
struct CachedLayoutComponent: LayoutProtocol {
    private var lastBounds: CGRect = .zero
    private var cachedFrames: [CGRect] = []
    
    func calculateLayout(bounds: CGRect) -> [CGRect] {
        // 如果边界未改变,返回缓存结果
        if bounds == lastBounds && !cachedFrames.isEmpty {
            return cachedFrames
        }
        
        // 计算新布局
        let newFrames = expensiveLayoutCalculation(bounds: bounds)
        
        // 更新缓存
        lastBounds = bounds
        cachedFrames = newFrames
        
        return newFrames
    }
    
    private func expensiveLayoutCalculation(bounds: CGRect) -> [CGRect] {
        // 耗时的布局计算
        // ...
    }
}
  1. 使用值类型:优先使用structenum实现协议,避免引用类型的性能开销:
// 推荐:值类型实现
struct ValueTypeParser: DataParser {
    // ...
}

// 不推荐:引用类型实现(除非必要)
class ReferenceTypeParser: DataParser {
    // ...
}

常见错误与解决方案

常见错误解决方案
模板方法被子类重写使用final关键字保护模板方法
原语操作未实现在协议中定义为func(必须实现)或提供默认实现
状态共享导致的线程安全问题使用Actor或加锁机制保护共享状态
过度设计:创建不必要的钩子方法遵循YAGNI原则,只在确认需要时才添加钩子
协议要求过多导致实现复杂将大协议拆分为多个小协议,使用协议组合

总结与展望

模板方法模式是Swift开发中实现代码复用与扩展的强大工具,它通过分离算法骨架与具体实现,使代码更加清晰、可维护。本文从理论到实践,全面介绍了模板方法模式的:

  • 核心概念:算法骨架与步骤延迟实现的设计思想
  • Swift实现:协议扩展与抽象类两种方式的对比与取舍
  • 标准库应用:深入分析SequenceCollection协议的模板方法设计
  • 实战场景:网络请求、数据解析、UI渲染三个场景的完整实现
  • 最佳实践:内存管理、性能优化与常见错误解决方案

随着Swift语言的发展,特别是Swift 5.5引入的async/await并发模型,模板方法模式也有了新的应用可能。例如,可以创建异步的模板方法,定义异步算法的骨架:

protocol AsyncDataProcessor {
    func fetchData() async throws -> Data
    func processData(_ data: Data) throws -> ProcessedResult
    
    // 异步模板方法
    func completeProcess() async throws -> ProcessedResult
}

extension AsyncDataProcessor {
    func completeProcess() async throws -> ProcessedResult {
        let data = try await fetchData()
        return try processData(data)
    }
}

通过掌握模板方法模式,你将能够设计出更加灵活、可扩展的系统架构,在面对复杂业务需求时更加游刃有余。

下一步学习建议:结合策略模式(Strategy Pattern)和工厂方法模式(Factory Method Pattern),构建更加强大灵活的系统。这些模式常与模板方法模式结合使用,解决更复杂的设计问题。

参考资料与进一步阅读

  1. 《设计模式:可复用面向对象软件的基础》(Gamma et al.)
  2. Swift标准库文档
  3. Swift by Sundell - 模板方法模式
  4. Swift设计模式 - GitHub仓库
  5. Swift API Design Guidelines

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值