iOS设计模式-工厂模式

127 阅读5分钟

工厂模式

核心:

解耦创建逻辑 · 多种子类 · 条件分支创建

思想及适用场景:

主要用于封装对象的创建逻辑,调用方只关心"我要什么",不关心"怎么造"。

当需要根据条件创建不同子类时尤其有用。

角色

工厂模式是,一个工厂生产一种产品。

  • 产品协议:定义产品能力、特点

  • 产品:遵循协议的产品类/struct

  • 工厂协议:定义生产方法

  • 工厂:遵守协议,实现生产

示例

1、产品侧:创建 协议 及其 产品类(实现产品功能)

例:猫,狗

    // ─── 产品协议 ───
    protocol Animal {
        var name: String { get }
        func speak() -> String
    }

    // ─── 具体产品 ───
    struct Dog: Animal {
        let name = "小狗"
        func speak() -> String { "汪汪!" }
    }

    struct Cat: Animal {
        let name = "小猫"
        func speak() -> String { "喵喵!" }
    }

2、工厂侧:创建 协议 及其 工厂类(实现生产方法,创建对应产品)

例:猫工厂 生产猫;狗工厂 生产狗

    // ─── 工厂协议(Creator) ───
    protocol AnimalFactory {
        func createAnimal() -> Animal   // 工厂方法
    }

    // ─── 具体工厂 ───
    struct DogFactory: AnimalFactory {
        func createAnimal() -> Animal { Dog() }
    }

    struct CatFactory: AnimalFactory {
        func createAnimal() -> Animal { Cat() }
    }

3、外界,只需根据条件,要什么产品,就传入对应工厂(内部创建对应产品)

    // ─── 客户端 ───
    enum Scense {
        case cat,
        case dog
    }

    func makeAnimalSpeak(factory: AnimalFactory) {
        let animal = factory.createAnimal()
        print("\(animal.name) 说:\(animal.speak())")
    }

    func make(for scense: Scense){
        switch scense:{
        case cat:
            makeAnimalSpeak(factory: DogFactory())  // 小狗 说:汪汪
        case dog:
            makeAnimalSpeak(factory: CatFactory())  // 小猫 说:喵喵
        }
    }

进阶:抽象工厂模式

思路:

在工厂模式基础上再抽象一层。普通工厂只生产一种产品,抽象工厂负责生产一族相关产品,保证同一工厂出来的产品风格一致,且调用方完全不依赖具体类。

核心区别一句话:工厂方法解决"造哪个",抽象工厂解决"造哪套"。

角色

  • 多个产品协议:定义每种产品的功能、特点

  • 遵守协议的类/struct:具体产品

  • 工厂协议:定义每种产品的生产方法

  • 遵守协议的类/struct:生产自己产品的 工厂

示例

抽象产品协议

// 产品族 协议
protocol Button {
    func render()
    func onTap()
}

protocol TextField {
    func render()
    func onInput(_ text: String)
}

// MARK: - iOS 具体产品
struct IOSButton: Button {
    func render()  { print("  🔵 渲染 iOS 圆角按钮") }
    func onTap()   { print("  📳 iOS Haptic 触感反馈") }
}

struct IOSTextField: TextField {
    func render()             { print("  🔵 渲染 iOS 下划线输入框") }
    func onInput(_ text: String) { print("  iOS 输入: \(text)") }
}

// MARK: - Android 具体产品
struct AndroidButton: Button {
    func render()  { print("  🟢 渲染 Material Design 按钮") }
    func onTap()   { print("  💧 Android Ripple 水波动效") }
}

struct AndroidTextField: TextField {
    func render()             { print("  🟢 渲染 Material 边框输入框") }
    func onInput(_ text: String) { print("  Android 输入: \(text)") }
}

抽象工厂协议

protocol UIFactory {
    func makeButton() -> Button
    func makeTextField() -> TextField
}

// 具体工厂(每个工厂只生产 自己的产品族)

struct IOSFactory: UIFactory {
    func makeButton()    -> Button    { IOSButton() }
    func makeTextField() -> TextField { IOSTextField() }
}

struct AndroidFactory: UIFactory {
    func makeButton()    -> Button    { AndroidButton() }
    func makeTextField() -> TextField { AndroidTextField() }
}

调用,客户端(只依赖抽象,不知道任何具体类型)

class LoginVC {
    private let button: Button
    private let textField: TextField

    // 注入工厂,由外部决定平台风格
    init(factory: UIFactory) {
        self.button    = factory.makeButton()
        self.textField = factory.makeTextField()
    }

    func showUI() {
        // 渲染登录界面
        textField.render()
        textField.onInput("alice@example.com")
        button.render()
        button.onTap()
    }
}

// MARK: - 运行
LoginVC(factory: IOSFactory()).showUI()
LoginVC(factory: AndroidFactory()).showUI()
  • 扩展

只需 新增 品类 和 工厂。不改 旧代码,不改调用方式(开闭原则),只需传递 新变量

// 新增 macOS 产品
struct MacOSButton: Button {
    func render() { print("  🖥 渲染 macOS NSButton") }
    func onTap()  { print("  🖱 macOS 点击事件") }
}

struct MacOSTextField: TextField {
    func render()                { print("  🖥 渲染 macOS NSTextField") }
    func onInput(_ text: String) { print("  macOS 输入: \(text)") }
}

// 新增工厂
struct MacOSFactory: UIFactory {
    func makeButton()    -> Button    { MacOSButton() }
    func makeTextField() -> TextField { MacOSTextField() }
}

// 使用 —— LoginScreen 代码零修改
LoginScreen(factory: MacOSFactory()).show()

适用场景总结:

当系统需要独立于产品创建方式、且需要保证同一产品族的一致性时——比如跨平台 UI 套件、换肤主题、多数据库驱动(SQLite / CoreData / CloudKit)——抽象工厂是最合适的选择。

对比

image.png

实战场景

1、工厂模式 - 网络请求封装:根据环境(Mock / Real)切换网络层

// 实战:根据环境(Mock / Real)切换网络层
protocol NetworkSession {
    func dataTask(with url: URL) async throws -> Data
}

struct LiveSession: NetworkSession {
    func dataTask(with url: URL) async throws -> Data {
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
}

struct MockSession: NetworkSession {
    func dataTask(with url: URL) async throws -> Data {
        // 返回本地 JSON,不真正发网络请求
        return """
        {"id": 1, "name": "Mock User"}
        """.data(using: .utf8)!
    }
}

// 工厂方法
protocol SessionFactory {
    func makeSession() -> NetworkSession
}

struct LiveSessionFactory: SessionFactory {
    func makeSession() -> NetworkSession { LiveSession() }
}

struct MockSessionFactory: SessionFactory {
    func makeSession() -> NetworkSession { MockSession() }
}

class UserRepository {
    private let session: NetworkSession
    init(factory: SessionFactory) {
        self.session = factory.makeSession()
    }
    // ...
}

// 单元测试注入 Mock,生产注入 Live
let repo = UserRepository(factory: MockSessionFactory())

2、抽象工厂 - 封装 多主题 UI 系统(Dark / Light)

// 实战:一键切换整套 App 主题
protocol ThemeColor {
    var primary: String { get }
    var background: String { get }
    var text: String { get }
}

protocol ThemeFont {
    var titleSize: CGFloat { get }
    var bodySize: CGFloat { get }
}

// 主题产品族 A:Light
struct LightColor: ThemeColor {
    var primary    = "#007AFF"
    var background = "#FFFFFF"
    var text       = "#000000"
}

struct LightFont: ThemeFont {
    var titleSize: CGFloat = 20
    var bodySize: CGFloat  = 16
}

// 主题产品族 B:Dark
struct DarkColor: ThemeColor {
    var primary    = "#0A84FF"
    var background = "#1C1C1E"
    var text       = "#FFFFFF"
}

struct DarkFont: ThemeFont {
    var titleSize: CGFloat = 22   // 暗色模式字体稍大
    var bodySize: CGFloat  = 17
}

// 抽象工厂
protocol ThemeFactory {
    func makeColor() -> ThemeColor
    func makeFont() -> ThemeFont
}

struct LightThemeFactory: ThemeFactory {
    func makeColor() -> ThemeColor { LightColor() }
    func makeFont() -> ThemeFont   { LightFont() }
}

struct DarkThemeFactory: ThemeFactory {
    func makeColor() -> ThemeColor { DarkColor() }
    func makeFont() -> ThemeFont   { DarkFont() }
}

// App 全局主题管理
class ThemeManager {
    static var shared = ThemeManager(factory: LightThemeFactory())

    let color: ThemeColor
    let font: ThemeFont

    init(factory: ThemeFactory) {
        self.color = factory.makeColor()
        self.font  = factory.makeFont()
    }
}

// 切换到深色主题,所有 color/font 同步更新
ThemeManager.shared = ThemeManager(factory: DarkThemeFactory())

总结

实际项目中两者常常配合出现:抽象工厂内部的每个 createXxx() 方法,本身就是一个工厂方法。抽象工厂是"工厂的工厂",是工厂方法向多产品维度的自然扩展。