iOS设计模式-装饰器

131 阅读3分钟

概念

装饰器模式是一种结构型设计模式,它允许你在不修改原有类的情况下,通过"包裹"的方式动态地为对象添加新功能。与继承不同,它在运行时灵活组合,符合开闭原则(对扩展开放,对修改关闭)。

适用场景

适用于 在基本功能基础上,进行功能叠加场景。

角色

协议:行为抽象

功能组件:遵守协议,实现基础功能

装饰器基类:遵守协议,持有功能组件,调用转发

装饰器扩展:继承自装饰器基类,通过重载,进行行为扩展(功能叠加);每个装饰器只做一件事

image.png

示例:咖啡配料系统

image.png

定义协议

protocol Coffee {
    func description() -> String
    func cost() -> Double
}

功能组件,遵守协议。

实现基本功能。

class PlainCoffee: Coffee {
    func description() -> String { "简单咖啡" }
    func cost() -> Double { 10.0 }
}

基础装饰器

1、持有引用功能组件,同时也遵守协议,转发调用

2、作为基类

class CoffeeDecorator: Coffee {

    private let decoratedCoffee: Coffee

    init(_ coffee: Coffee) {
        self.decoratedCoffee = coffee
    }

    // 协议调用 转发
    func description() -> String {
        decoratedCoffee.description()
    }

    func cost() -> Double {
        decoratedCoffee.cost()
    }
}

具体装饰器(扩展行为)

继承自 基础装饰器,重载 协议方法;

  • 职责单一: 每个装饰器只做一件事,(每种功能分开写在一个子类中
class MilkDecorator: CoffeeDecorator {
    override func description() -> String {
        super.description() + ",加牛奶"
    }
    override func cost() -> Double {
        super.cost() + 5.0
    }
}

class SugarDecorator: CoffeeDecorator {
    override func description() -> String {
        super.description() + ",加糖"
    }
    override func cost() -> Double {
        super.cost() + 3.0
    }
}

class VanillaDecorator: CoffeeDecorator {
    override func description() -> String {
        super.description() + ",香草风味"
    }
    override func cost() -> Double {
        super.cost() + 8.0
    }
}
  • 灵活组合

通过构造器注入,功能一层一层加

let plain = PlainCoffee()
print(plain.description()) // 简单咖啡
print(plain.cost())        // 10.0

// 组合 叠加
let milkCoffee = MilkDecorator(plain)
let sugerCoffee = SugarDecorator(milkCoffee)
let fancyCoffee = VanillaDecorator(sugerCoffee)

print(fancyCoffee.description())
// 简单咖啡,加牛奶,加糖,香草风味

print(fancyCoffee.cost())
// 26.0(10 + 5 + 3 + 8)
  • 可扩展性: 

需要加其他功能?只需再写一个对应的装饰器,插入链中,其他代码零修改。

对比

image.png

应用示例——网络请求层

真实 App 的网络层需要叠加多种能力:鉴权、日志、重试、缓存……如果全写在一个类里,既臃肿又难测试。装饰器模式让每个能力独立成一层,按需叠加。

1、协议 — 统一接口

所有网络客户端(真实 + 装饰器)共同遵循的协议

import Foundation

protocol HTTPClient {
    func send(
        _ request: URLRequest,
        completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void
    )
}

2、真实组件 — URLSession 封装

final class URLSessionHTTPClient: HTTPClient {
    private let session: URLSession

    init(session: URLSession = .shared) {
        self.session = session
    }

    func send(
        _ request: URLRequest,
        completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void
    ) {
        session.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            guard let data = data,
                  let response = response as? HTTPURLResponse else {
                completion(.failure(URLError(.badServerResponse)))
                return
            }
            completion(.success((data, response)))
        }.resume()
    }
}

3、装饰器1:日志(LogDecorator)

final class LogDecorator: HTTPClient {
    private let wrapped: HTTPClient

    init(_ client: HTTPClient) {
        self.wrapped = client
    }

    func send(
        _ request: URLRequest,
        completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void
    ) {
        let start = Date()
        print("▶ [\(request.httpMethod ?? "?")] \(request.url?.absoluteString ?? "")")

        wrapped.send(request) { result in
            let elapsed = String(format: "%.2fms", Date().timeIntervalSince(start) * 1000)
            switch result {
            case .success(let (data, response)):
                print("✅ \(response.statusCode) | \(data.count)B | \(elapsed)")
            case .failure(let error):
                print("❌ 失败: \(error.localizedDescription) | \(elapsed)")
            }
            completion(result)
        }
    }
}

4、装饰器2:自动鉴权(AuthDecorator)

final class AuthDecorator: HTTPClient {
    private let wrapped: HTTPClient
    private let tokenProvider: () -> String?

    init(_ client: HTTPClient, tokenProvider: @escaping () -> String?) {
        self.wrapped = client
        self.tokenProvider = tokenProvider
    }

    func send(
        _ request: URLRequest,
        completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void
    ) {
        var authenticatedRequest = request
        if let token = tokenProvider() {
            authenticatedRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }
        wrapped.send(authenticatedRequest, completion: completion)
    }
}

5、装饰器三:失败重试(RetryDecorator)

final class RetryDecorator: HTTPClient {
    private let wrapped: HTTPClient
    private let maxRetries: Int

    init(_ client: HTTPClient, maxRetries: Int = 3) {
        self.wrapped = client
        self.maxRetries = maxRetries
    }

    func send(
        _ request: URLRequest,
        completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void
    ) {
        attempt(request, retriesLeft: maxRetries, completion: completion)
    }

    private func attempt(
        _ request: URLRequest,
        retriesLeft: Int,
        completion: @escaping (Result<(Data, HTTPURLResponse), Error>) -> Void
    ) {
        wrapped.send(request) { [weak self] result in
            guard let self else { return }
            switch result {
            case .success:
                completion(result)
            case .failure(let error):
                guard retriesLeft > 0 else {
                    completion(.failure(error))
                    return
                }
                print("🔁 重试,剩余次数: \(retriesLeft - 1)")
                // 延迟 1s 后重试
                DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
                    self.attempt(request, retriesLeft: retriesLeft - 1, completion: completion)
                }
            }
        }
    }
}

6、组装

// AppDelegate 或 DI 容器中
func makeHTTPClient() -> HTTPClient {
    // 最内层:真实请求
    let base = URLSessionHTTPClient()           
    // 第一层:日志
    let logged = LogDecorator(base)             
    // 第二层:鉴权
    let authed = AuthDecorator(logged) {        
        KeychainManager.shared.accessToken
    }
    // 第三层:重试
    let retrying = RetryDecorator(authed, maxRetries: 3)

    return retrying  // 业务层只看到 HTTPClient
}

7、调用

final class UserViewModel {
    private let client: HTTPClient    // 只依赖协议

    init(client: HTTPClient) {
        self.client = client
    }

    func fetchProfile(userId: String) {
        var request = URLRequest(url: URL(string: "https://api.example.com/users/\(userId)")!)
        request.httpMethod = "GET"

        client.send(request) { result in
            switch result {
            case .success(let (data, _)):
                // 解析 data...
                break
            case .failure(let error):
                // 处理错误...
                break
            }
        }
    }
}

8、扩展

需要加缓存层?只需再写一个 CacheDecorator,插入链中

let cached = CacheDecorator(authed, cache: URLCache.shared)
let retrying = RetryDecorator(cached)