工廠模式 Factory Pattern

工廠模式(Factory Pattern)屬于創(chuàng)建型模式(Creational Pattern),F(xiàn)actory pattern 提供了一種在不暴露創(chuàng)建邏輯的情況下創(chuàng)建對象的方法。包含以下兩部分:

FactoryPatternUML.png
  1. Factory:創(chuàng)建對象。
  2. Products:被創(chuàng)建的對象。

從技術(shù)上講,工廠模式分為簡單工廠(Simple Factory)、抽象工廠(Abstract Factory)和其他。幾種模式的共同目標(biāo)是:將創(chuàng)建對象的邏輯封裝到自身構(gòu)造中。

1. 何時使用 factory pattern

當(dāng)想要分離產(chǎn)品創(chuàng)建邏輯,而非讓消費者(consumer)直接創(chuàng)建 product 時,使用工廠模式。

當(dāng)您擁有一組相關(guān)對象(例如多態(tài)子類,或?qū)崿F(xiàn)相同協(xié)議的多個對象)時,factory pattern 非常有用。例如,可以使用 factory pattern 檢查網(wǎng)絡(luò)響應(yīng),并將其轉(zhuǎn)換為具體的 model。

當(dāng)只有一種產(chǎn)品類型,且需要提供依賴或額外信息才能創(chuàng)建時,工廠模式也非常有用。例如,可以使用 factory pattern 創(chuàng)建一個自動回復(fù)求職者的郵件系統(tǒng),factory pattern 根據(jù)候選人被錄取、拒絕、邀請面試等狀態(tài)來生成具體的郵件信息。

2. Simple Factory

這一部分將會使用 simple factory 創(chuàng)建一個回復(fù)求職者的郵件系統(tǒng)。

先聲明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
}

每位求職者有name、email和四種不同的求職狀態(tài)。EmailsubjectmessageBody將根據(jù)求職狀態(tài)而變。

添加以下工廠方法:

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)
    }
}

在上面的代碼中,先創(chuàng)建EmailFactory struct,并聲明一個 public 屬性senderEmail。在EmailFactory的初始化中會設(shè)置該屬性。createEmail(to recipient:)函數(shù)接受一個JobApplicant實參,返回Email,在createEmail(to recipient:)函數(shù)內(nèi),添加 switch 枚舉status,根據(jù)status不同生成不同subjectmessageBody。

現(xiàn)在,郵件模版系統(tǒng)已經(jīng)構(gòu)建完成。是時候?qū)⒃?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")

這里創(chuàng)建了JobApplicantEmailFactory,使用emailFactory實例生成 email。

3. Abstract Factory

這一部分將介紹 abstract factory。與簡單工廠相比,抽象工廠處理更為復(fù)雜的情況。

假設(shè)我們要創(chuàng)建 Android 和 iOS 兩種類型的按鈕,我們先定義一個AbstractGUIFactory類,再定義具體的工廠類創(chuàng)建 Android 和 iOS 兩種類型的按鈕,具體的工廠類實現(xiàn)創(chuàng)建方法。如果需要創(chuàng)建其他類型控件(例如 alert),只需要在具體工廠類中添加創(chuàng)建方法即可。

聲明Button協(xié)議:

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

定義AndroidButtoniOSButton兩個類,該類遵守Button協(xié)議。

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")")
    }
}

創(chuàng)建抽象工廠類AbstractGUIFactory,代碼如下:

protocol AbstractGUIFactory {
    func createButton() -> Button
}

創(chuàng)建AndroidFactoryiOSFactory具體工廠類,如下所示:

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

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

最后,再創(chuàng)建一個GUIBuilder負(fù)責(zé)創(chuàng)建具體控件:

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()
    }
}

下面,使用該工廠方法創(chuàng)建控件:

        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.

當(dāng)我們想要創(chuàng)建其他控件時,只需要添加控件,在具體工廠方法中增加創(chuàng)建方法即可。例如,想要增加AndroidAlertiOSAlert兩種類型控件,只需聲明兩種類型控件,在具體工廠中添加創(chuàng)建方法即可。

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

// AndroidAlert和iOSAlert均遵守Alert協(xié)議。
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 {
    ...
    
    // 增加創(chuàng)建 alert 方法
    func createAlert() -> Alert {
        return AndroidAlert()
    }
}

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

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

        // 具體應(yīng)用
        let androidAlert = androidBuilder.buildAlert()
        androidAlert.setTitle("github.com/pro648")
        androidAlert.show()
        
        let iOSAlert = iOSBuilder.buildAlert()
        iOSAlert.setTitle("Knowledge is power.")
        iOSAlert.show()

并不是所有多態(tài)對象都需要 factory pattern。如果對象非常簡單,則始終可以將創(chuàng)建邏輯放到 consumer 中(例如,視圖控制器)。

如果對象需要連續(xù)步驟來創(chuàng)建,生成器模式(Builder Patter)或許更為合適。

總結(jié)

以下是 factory design pattern 的關(guān)鍵點:

  • Factory 的目標(biāo)是將創(chuàng)建對象的邏輯隔離到自身構(gòu)造中。
  • 如果您擁有一組相關(guān) product,或在提供更多信息前(例如,接收到 response,或用戶輸入內(nèi)容)無法創(chuàng)建對象,則工廠模式非常有用。
  • Factory design pattern 添加了一層抽象來創(chuàng)建對象,能夠減少重復(fù)代碼。

通過 factory pattern 可以再次減少視圖控制器代碼,遵守 Open/closed principle,降低耦合性。

工廠模式 Factory Pattern策略模式 Strategy Pattern 有些相似,區(qū)別如下:

  • Factory Pattern:是 creational pattern,用于創(chuàng)建特定類型對象。例如,創(chuàng)建狗、貓、老虎等不同類型動物。
  • Strategy Pattern:是 behavioral pattern,以特定方式執(zhí)行操作。例如,執(zhí)行走、跑,跳等動作。

工廠模式和策略模式可以組合使用。例如,有一個創(chuàng)建 business 對象的工廠模式,其根據(jù)持久化策略不同選擇不同的工廠模式。如果數(shù)據(jù)保存到本地 XML ,使用 A 策略;如果數(shù)據(jù)保存到遠(yuǎn)程數(shù)據(jù)庫,使用 B 策略。

最為重要的是了解使用設(shè)計模式的動機,否則就像在木工店里用錘子切割木材。也就是說,在不適當(dāng)?shù)纳舷挛闹惺褂迷O(shè)計模式就是在反設(shè)計模式,因此請確保了解設(shè)計模式的動機。

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容