工厂模式 Factory Pattern

工厂模式(Factory Pattern)属于创建型模式(Creational Pattern),Factory pattern 提供了一种在不暴露创建逻辑的情况下创建对象的方法。包含以下两部分:

FactoryPatternUML.png
  1. Factory:创建对象。
  2. Products:被创建的对象。

从技术上讲,工厂模式分为简单工厂(Simple Factory)、抽象工厂(Abstract Factory)和其他。几种模式的共同目标是:将创建对象的逻辑封装到自身构造中。

1. 何时使用 factory pattern

当想要分离产品创建逻辑,而非让消费者(consumer)直接创建 product 时,使用工厂模式。

当您拥有一组相关对象(例如多态子类,或实现相同协议的多个对象)时,factory pattern 非常有用。例如,可以使用 factory pattern 检查网络响应,并将其转换为具体的 model。

当只有一种产品类型,且需要提供依赖或额外信息才能创建时,工厂模式也非常有用。例如,可以使用 factory pattern 创建一个自动回复求职者的邮件系统,factory pattern 根据候选人被录取、拒绝、邀请面试等状态来生成具体的邮件信息。

2. Simple Factory

这一部分将会使用 simple factory 创建一个回复求职者的邮件系统。

先声明JobApplicantEmailmodel,代码如下:

import Foundation

public struct JobApplicant {
    
    public let name: String
    public let email: String
    public var status: Status
    
    public enum Status {
        case new
        case interview
        case hired
        case rejected
    }
}

public struct Email {
    public let subject: String
    public let messageBody: String
    public let recipientEmail: String
    public let senderEmail: String
}

每位求职者有nameemail和四种不同的求职状态。EmailsubjectmessageBody将根据求职状态而变。

添加以下工厂方法:

import Foundation

public struct EmailFactory {
    public let senderEmail: String
    
    public func createEmail(to recipient: JobApplicant) -> Email {
        let subject: String
        let messageBody: String
        
        switch recipient.status {
        case .new:
            subject = "We Received Your Application"
            messageBody = "Thanks for applying for a job here!" + "You should hear from us in 1-3 business days."
            
        case .interview:
            subject = "We Want to Interview You"
            messageBody = "Thanks for your resume,\(recipient.name)!" + "Can you come in for an interview in 30 minutes?"
            
        case .hired:
            subject = "We Want to Hire You"
            messageBody = "Congratulations, \(recipient.name)!" + "We liked your code, and you smelled nice." + "We want to offer you a position! Cha-ching! $$$"
            
        case .rejected:
            subject = "Thanks for Your Application"
            messageBody = "Thank you for applying,\(recipient.name)!" + "We have decided to move forward with other candidates." + "Please remeber to wear pants next time!"
        }
        
        return Email(subject: subject,
                     messageBody: messageBody,
                     recipientEmail: recipient.email,
                     senderEmail: senderEmail)
    }
}

在上面的代码中,先创建EmailFactory struct,并声明一个 public 属性senderEmail。在EmailFactory的初始化中会设置该属性。createEmail(to recipient:)函数接受一个JobApplicant实参,返回Email,在createEmail(to recipient:)函数内,添加 switch 枚举status,根据status不同生成不同subjectmessageBody

现在,邮件模版系统已经构建完成。是时候将该 factory pattern 用于未来的申请人了。添加以下代码:

        var pro648 = JobApplicant(name: "pro648",
                                   email: "pro648@example.com",
                                   status: .new)
        let emailFactory = EmailFactory(senderEmail: "about@example.com")
        
        print(emailFactory.createEmail(to: pro648), "\n")
        
        pro648.status = .interview
        print(emailFactory.createEmail(to: pro648), "\n")
        
        pro648.status = .hired
        print(emailFactory.createEmail(to: pro648), "\n")

这里创建了JobApplicantEmailFactory,使用emailFactory实例生成 email。

3. Abstract Factory

这一部分将介绍 abstract factory。与简单工厂相比,抽象工厂处理更为复杂的情况。

假设我们要创建 Android 和 iOS 两种类型的按钮,我们先定义一个AbstractGUIFactory类,再定义具体的工厂类创建 Android 和 iOS 两种类型的按钮,具体的工厂类实现创建方法。如果需要创建其他类型控件(例如 alert),只需要在具体工厂类中添加创建方法即可。

声明Button协议:

protocol Button {
    func setTitle(_ title: String) -> Void
    func show() -> Void
}

定义AndroidButtoniOSButton两个类,该类遵守Button协议。

class AndroidButton: Button {
    private var title: String?
    
    func setTitle(_ title: String) {
        self.title = title
    }
    
    func show() {
        print("Showing Android style button. Title: \(title ?? "Default Title")")
    }
}


class iOSButton: Button {
    private var title: String?
    
    func setTitle(_ title: String) {
        self.title = title
    }
    
    func show() {
        print("Showing iOS style button. Title: \(title ?? "Default Title")")
    }
}

创建抽象工厂类AbstractGUIFactory,代码如下:

protocol AbstractGUIFactory {
    func createButton() -> Button
}

创建AndroidFactoryiOSFactory具体工厂类,如下所示:

class AndroidFactory: AbstractGUIFactory {
    func createButton() -> Button {
        return AndroidButton()
    }
}

class iOSFactory: AbstractGUIFactory {
    func createButton() -> Button {
        return iOSButton()
    }
}

最后,再创建一个GUIBuilder负责创建具体控件:

class GUIBuilder {
    private var style: Style
    private var guiFactory: AbstractGUIFactory?
    
    public enum Style {
        case iOS
        case Android
    }
    
    init(style: Style) {
        self.style = style
    }
    
    func initGUIFactory() -> Void {
        if nil != guiFactory {
            return
        }
        
        switch style {
        case .iOS:
            guiFactory = iOSFactory()
        case .Android:
            guiFactory = AndroidFactory()
        }
    }
    
    func buildButton() -> Button {
        initGUIFactory()
        return guiFactory!.createButton()
    }
}

下面,使用该工厂方法创建控件:

        let androidBuilder = GUIBuilder(style: .Android)
        
        let androidButton = androidBuilder.buildButton()
        androidButton.setTitle("Be together, Not the same.")
        androidButton.show()
        
        let iOSBuilder = GUIBuilder(style: .iOS)
        
        let iOSButton = iOSBuilder.buildButton()
        iOSButton.setTitle("Power is power.")
        iOSButton.show()

运行后输出如下:

Showing Android style button. Title: Be together, Not the same.
Showing iOS style button. Title: Power is power.

当我们想要创建其他控件时,只需要添加控件,在具体工厂方法中增加创建方法即可。例如,想要增加AndroidAlertiOSAlert两种类型控件,只需声明两种类型控件,在具体工厂中添加创建方法即可。

protocol Alert {
    func setTitle(_ title: String) -> Void
    func show() -> Void
}

// AndroidAlert和iOSAlert均遵守Alert协议。
class AndroidAlert: Alert {
    private var title: String?
    
    func setTitle(_ title: String) {
        self.title = title
    }
    
    func show() {
        print("Showing Android style Alert. Title: \(title ?? "Default Title")")
    }
}

class iOSAlert: Alert {
    private var title: String?
    
    func setTitle(_ title: String) {
        self.title = title
    }
    
    func show() {
        print("Showing iOS style alert. Title: \(title ?? "Default Title")")
    }
}

class AndroidFactory: AbstractGUIFactory {
    ...
    
    // 增加创建 alert 方法
    func createAlert() -> Alert {
        return AndroidAlert()
    }
}

class iOSFactory: AbstractGUIFactory {
    ...
    
    // 增加创建 alert
    func createAlert() -> Alert {
        return iOSAlert()
    }
}

class GUIBuilder {
    ...
    
    // 增加创建alert
    func buildAlert() -> Alert {
        initGUIFactory()
        return guiFactory!.createAlert()
    }
}

        // 具体应用
        let androidAlert = androidBuilder.buildAlert()
        androidAlert.setTitle("github.com/pro648")
        androidAlert.show()
        
        let iOSAlert = iOSBuilder.buildAlert()
        iOSAlert.setTitle("Knowledge is power.")
        iOSAlert.show()

并不是所有多态对象都需要 factory pattern。如果对象非常简单,则始终可以将创建逻辑放到 consumer 中(例如,视图控制器)。

如果对象需要连续步骤来创建,生成器模式(Builder Patter)或许更为合适。

总结

以下是 factory design pattern 的关键点:

  • Factory 的目标是将创建对象的逻辑隔离到自身构造中。
  • 如果您拥有一组相关 product,或在提供更多信息前(例如,接收到 response,或用户输入内容)无法创建对象,则工厂模式非常有用。
  • Factory design pattern 添加了一层抽象来创建对象,能够减少重复代码。

通过 factory pattern 可以再次减少视图控制器代码,遵守 Open/closed principle,降低耦合性。

工厂模式 Factory Pattern策略模式 Strategy Pattern 有些相似,区别如下:

  • Factory Pattern:是 creational pattern,用于创建特定类型对象。例如,创建狗、猫、老虎等不同类型动物。
  • Strategy Pattern:是 behavioral pattern,以特定方式执行操作。例如,执行走、跑,跳等动作。

工厂模式和策略模式可以组合使用。例如,有一个创建 business 对象的工厂模式,其根据持久化策略不同选择不同的工厂模式。如果数据保存到本地 XML ,使用 A 策略;如果数据保存到远程数据库,使用 B 策略。

最为重要的是了解使用设计模式的动机,否则就像在木工店里用锤子切割木材。也就是说,在不适当的上下文中使用设计模式就是在反设计模式,因此请确保了解设计模式的动机。

SimpleFactory 源码地址:https://github.com/pro648/BasicDemos-iOS/tree/master/SimpleFactory

AbstractFactory 源码地址:https://github.com/pro648/BasicDemos-iOS/tree/master/AbstractFactory

参考资料:

  1. Swift World: Design Patterns — Abstract Factory

  2. Swift simple factory design pattern

  3. Design Patterns — Creational Patterns — Factory Pattern in Swift

  4. AbstractFactory

  5. What is the difference between Factory and Strategy patterns?

  6. Difference of Strategy Pattern with Factory Method?

欢迎更多指正:https://github.com/pro648/tips/wiki

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容