設計模式 - 深入淺出工廠模式

factory.jpg

工廠模式概述

工廠模式是設計模式的一種,從功能上來說,它的主要作用是創(chuàng)建對象。細分一下,有三種不同類型的工廠模式,即,

  • 簡單工廠模式
  • 工廠模式
  • 抽象工廠模式

本文使用swift編碼,試圖通過簡單的代碼和配圖簡要說明工廠模式的使用。既然是關于工廠模式,那么直接映射到現(xiàn)實世界,就用汽車工廠生產(chǎn)汽車這樣一個模型來描述工廠模式的使用和逐步改進的過程。

創(chuàng)建對象存在的問題

這里假設,工廠可以生產(chǎn)汽車(Car),這個數(shù)據(jù)模型可以用swift像下面這樣定義,

protocol VehicleFeature {
    func speedUp() -> Void
    func numberOfWheelNumber() -> Int
}

class Vehicle: VehicleFeature {
    private var name: String? = "Vehicle"
    private var wheelNumber: Int?
        func speedUp() {
        print("car speed up")
    }
    
    func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

class Car: Vehicle {
    override func speedUp() {
        print("car speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

上面代碼定義了VehicleFeature協(xié)議,該協(xié)議中定義了-speedUp和-numberOfWheelNumber兩個方法,父類Vehicle定義了name和wheelNumber兩個可選值屬性,并且Vehicle實現(xiàn)了VehicleFeature協(xié)議,子類Car繼承父類Vehicle。

這時客戶想要一輛汽車Car,直接調用Car的初始化方法let car = Car(),即可創(chuàng)建一個Car對象。

想一想,隨著工廠業(yè)務的擴張,這時候還需要生產(chǎn)摩托車(MotorBicycle)和大巴車(Bus),我們得新增加MotorBicycle和Bus兩個類,如下代碼所示,

class MotorBicycle: Vehicle {
    
    override func speedUp() {
        print("motorBicycle speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}


class Bus: Vehicle {
    
    override func speedUp() {
        print("bus speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

通過let motorBicycle = MotorBicycle(); let bus = Bus()即可為客戶創(chuàng)建MotorBicycle和Bus對象。

在開發(fā)過程中,通過上述方式創(chuàng)建對象是一種很常見的方法,不過我們應該意識到,隨著業(yè)務的調整和擴展,類似let car = Car()這樣的對象初始化方法會散落在項目的各個角落,代碼維護的成本越來越高,比如現(xiàn)在給父類Vehicle添加一個-required init方法,用以在初始化時候指定車輛(Vehicle)車輪數(shù)量,代碼如下,

class Vehicle: VehicleFeature {
    private var name: String? = "Vehicle"
    private var wheelNumber: Int?
    
    required init(wheelNumber: Int) {
        self.wheelNumber = wheelNumber
    }
    
    func speedUp() {
        print("car speed up")
    }
    
    func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

在swift中,如果父類定義了-required init方法,那么子類定義-init方法時必須要定義同樣的-required init方法,所以上述的Car、MotorBicycle和Bus三個子類,也要添加跟父類Vehicle同樣的-required init方法,代碼如下,

class Car: Vehicle {
    
    required init(wheelNumber: Int) {
        super.init(wheelNumber: wheelNumber)
    }
    
    override func speedUp() {
        print("car speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

class MotorBicycle: Vehicle {
    
    required init(wheelNumber: Int) {
        super.init(wheelNumber: wheelNumber)
    }
    
    override func speedUp() {
        print("motorBicycle speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

class Bus: Vehicle {
    
    required init(wheelNumber: Int) {
        super.init(wheelNumber: wheelNumber)
    }
    
    override func speedUp() {
        print("bus speed up")
    }
    
    override func numberOfWheelNumber() -> Int {
        if let wheelNumber = self.wheelNumber {
            return wheelNumber
        }
        return 0
    }
}

經(jīng)過上面的更改,我們需要通過帶參數(shù)的初始話方法let car = Car(wheelNumber: 4)let motorBicycle = MotorBicycle(wheelNumber: 2)來創(chuàng)建對象,項目中大量的創(chuàng)建對象的代碼得重新編碼和調整。

簡單工廠模式

上面的demo,簡單說明了創(chuàng)建對象時候可能存在的問題,遇到這樣的問題,我們該怎么優(yōu)化呢?

這時候簡單工廠模式(Simple Factory Pattern)出現(xiàn)了,它可以短期內緩解我們的壓力。現(xiàn)在客戶需要一輛車Car,我們不再通過let car = ...來創(chuàng)建對象,而是將創(chuàng)建對象的任務交給簡單工廠,這時需要在該模型中增加一個新的角色簡單工廠(SimpleFactory),它的主要功能是根據(jù)客戶輸入的機車類型,為用戶創(chuàng)建對應的機車,下面的代碼展示了簡單工廠模式的使用,

enum FactoryVehicleType {
    case CarType
    case MotorBicycle
    case BusType
}

class SimpleFactory {
    func createVehicle(vehicleType: FactoryVehicleType) -> Vehicle {
        switch vehicleType {
        case .CarType:
            return Car(wheelNumber: 4)
        case .MotorBicycle:
            return MotorBicycle(wheelNumber: 2)
        case .BusType:
            return Bus(wheelNumber: 4)
        }
    }
}

上述代碼,定義了枚舉類型FactoryVehicleType,它描述了簡單工廠可以創(chuàng)建的幾種車型,新增加的SimpleFactory(簡單工廠類),它包含-createVehicle方法,該方法根據(jù)輸入的FactoryVehicle枚舉值類型,創(chuàng)建對應的車型。

上述代碼存在了這樣幾個角色:抽象產(chǎn)品(Abstract Product)、具體產(chǎn)品(Concrete Product)、簡單工廠(SimpleFactory)以及客戶類(Consumer),其中SimpleFactory是簡單工廠模式的核心類,它們之間的關系如下UML圖所示,

SimpleFactory.jpg

通過使用簡單工廠方法,為我們創(chuàng)建對象提供了方便,也節(jié)省了開發(fā)者的精力。假設現(xiàn)在改動了父類Vehicle的required init方法,各個子類也要做相應的改動,此時我們只需在SimpleFactory的-createVehicle方法中改動創(chuàng)建子類的初始化方法即可,而項目各個角落中通過SimpleFactory創(chuàng)建子類對象的零散的代碼都不需要做任何的改動,這無疑是一個大大的改進。

但是簡單工廠模式也存在很大的不足,那就是隨著工廠規(guī)模的增加和客戶的需求,現(xiàn)在需要生產(chǎn)更多類型的車輛,比如現(xiàn)在要增加生產(chǎn)坦克(Tank)和SUV,這時候我們得重新調整SimpleFactory,代碼如下,

enum FactoryVehicleType {
    case CarType
    case MotorBicycle
    case BusType
    case TankType
    case SUVType
}

class SimpleFactory {
    func createVehicle(vehicleType: FactoryVehicleType) -> Vehicle {
        switch vehicleType {
        case .CarType:
            return Car(wheelNumber: 4)
        case .MotorBicycle:
            return MotorBicycle(wheelNumber: 2)
        case .BusType:
            return Bus(wheelNumber: 4)
        case .TankType:
              return Tank(wheelNumber: 16)
        case .SUVType:
              return SUV(wheelNumber: 4)
        }
    }
}

可以想象,隨著工廠的發(fā)展,SimpleFactory的代碼會越來越臃腫,每增加一條新型車輛的生產(chǎn)線,就得新增加FactoryVehicleType枚舉類型的種類,同時更改SimpleFactory的-createVehicle方法。所以說,SimpleFactory只能短期內緩解工廠的壓力,這種方案并不是一個長久之計。

這么說來,SimpleFactory并不是像它的名字一樣看起來簡單和簡潔,這里的Simple也許僅僅代表了簡單工廠模式適合處理相對簡單的業(yè)務場景。粗略地看一下臃腫不堪的SimpleFactory代碼,我們可以說SimpleFactory之所以臃腫不堪,是因為它所承擔的職能太多了,它要負責創(chuàng)建Car、Bus和MotorBicycle,還要負責創(chuàng)建Tank和SUV,在將來它可能還要承擔更多更繁雜的職能,這顯然違背了軟件開發(fā)中的“單一職責”和“開閉原則”。

開閉原則:Open-Closed Principle,OCP) "Software entities should be open for extension,but closed for modification"。翻譯一下:“軟件實體應當對擴展開放,對修改關閉”。通俗點來說就是:軟件系統(tǒng)中包含的各種組件,例如模塊(Modules)、類(Classes)以及功能(Functions)等等,應該在不修改現(xiàn)有代碼的基礎上,引入新功能。開閉原則中“開”,是指對于組件功能的擴展是開放的,是允許對其進行功能擴展的;開閉原則中“閉”,是指對于原有代碼的修改是封閉的,即不應該修改原有的代碼。

怎樣優(yōu)化SimpleFactroy,其中的關鍵點和問題所在其實就是SimpleFactory職責太多、代碼臃腫,這時我們需要對SimpleFactory的職能進行拆分,將它創(chuàng)建車輛的各條業(yè)務線拆分給不同的工廠,這催生了FactoryPattern(工廠模式)。

工廠模式

FactoryPattern的出現(xiàn)就是為了簡化SimpleFactory,它也符合軟件開發(fā)和設計中的單一職責原則。就像現(xiàn)實世界中一樣,現(xiàn)在已經(jīng)不可能存在這樣一個巨型的工廠,它需要負責生產(chǎn)各種類型的汽車,我們需要的是具有單一職能并且高度專業(yè)化的工廠,這種單一職責的工廠也是從SimpleFactory拆分而來,那么對應于上面的代碼demo,我們拆分SimpleFactory,對于已經(jīng)定義的Car、Bus、MotorBicycle等數(shù)據(jù)模型則不需要改變,我們只需要增加對應的三個工廠類和一個抽象工廠協(xié)議(AbstractFactory),如下代碼所示,

protocol AbstractFactory {
    func createVehicle() -> Vehicle
}

class CarFactory: AbstractFactory {
    func createVehicle() -> Vehicle {
        return Car(wheelNumber: 4)
    }
}

class BusFactory: AbstractFactory {
    func createVehicle() -> Vehicle {
        return Bus(wheelNumber: 4)
    }
}

class MotorBicycleFactory: AbstractFactory {
    func createVehicle() -> Vehicle {
        return MotorBicycle(wheelNumber: 2)
    }
}

AbstractFactory是一個協(xié)議,它定義了一個創(chuàng)建Vehicle的-createVehicle方法,實現(xiàn)了該協(xié)議的三個具體工廠類(Concrete Factory)需要實現(xiàn)該協(xié)議中的-createVehicle方法,分別創(chuàng)建對應的車輛。

現(xiàn)在分別創(chuàng)建一個Car、Bus和MotorBicycle對象,我們可以用如下代碼實現(xiàn),

let car = CarFactory().createVehicle()
let bus = BusFactory().createVehicle()
let motorBicycle = MotorBicycleFactory().createVehicle()

這樣,每一種類型的車輛都有其對應的工廠類,當我們需要創(chuàng)建一種類型的車輛時,只需調用其對應的工廠類的-createVehicle方法。

備注:工廠類,例如CarFactory是負責創(chuàng)建Car對象的,上面的代碼存在一點點的瑕疵,那就是創(chuàng)建多個Car對象時,也通過CarFactory()多次創(chuàng)建CarFactory對象,但是它并不涉及到數(shù)據(jù)存儲和業(yè)務邏輯,這樣無疑消耗了額外的內存空間,為了性能考慮,可以將CarFactory、BusFactory和MotorBicycleFactory定義為單例模式。

可以看出,在工廠模式中,有這樣幾個角色:抽象產(chǎn)品(Abstract Product)、具體產(chǎn)品(Concrete Product)、抽象工廠(Abstract Factory)、具體工廠(Concrete Factory)以及客戶(Consumer),AbstractFactory是工廠模式的核心,它們之間的關系如下圖UML所示,

FactoryPattern.jpg

與簡單工廠模式相比,工廠模式再也不必煩惱于臃腫的代碼,如果需要制造新的機車類型,比如現(xiàn)在需要創(chuàng)建坦克和SUV,為了滿足這樣的需求,我們首先創(chuàng)建兩個繼承于Vehicle的兩個子類Tank和SUV,接著分別創(chuàng)建Tank和SUV對應的工廠類TankFactory和SUVFactory,使用了這樣的方法,不會改動原有的代碼結構,這樣也遵循了“開閉原則”。

工廠模式可以滿足大部分需求,但是,但是,重要的但是說三遍,但是,當我們將產(chǎn)品細分為不同的等級結構,例如Car這樣一個產(chǎn)品可以細分為發(fā)動機(Engine)、車門(Door)和電池(Battery)等幾個重要的等級結構;而Car本身也有很多類型,例如奔馳和奧迪,這種情況下,Car有兩種類型,每一種Car都分別有其對應使用的Engine, Door和Battery,如下表所列,

產(chǎn)品族 / 產(chǎn)品等級 奔馳 奧迪
發(fā)動機Engine BenzEngine AudiEngine
車門Door BenzDoor AudiDoor
電池Battery BenzBattery AudiBattery

這樣細分下來,為了使一輛車可以有效使用,必須保證所用的配件能夠匹配,想象一下生產(chǎn)的AudiEngine給Benz用,BenzDoor給Audi用,真是畫面太美不敢看。這時候給我們生產(chǎn)產(chǎn)品也帶來了問題,因為現(xiàn)在有兩種車類型 - Benz和Audi,每種車類型有三個重要配件 - Engine、Door和Battery,如果使用工廠模式生產(chǎn)配件,我們得創(chuàng)建6個對應的工廠,即BenzEngineFactory, BenzDoorFactory, BenzBatteryFactory和AudiEngineFactory, AudiDoorFactory, AudiBatteryFactory,這無疑是一個讓人無語的方案。所以說這種情況下,工廠模式已經(jīng)滿足不了當前的需求,我們需要進行更高一層次的抽象,這時候“抽象工廠模式”應運而生。

這里,筆者在討論生產(chǎn)時候偷換了概念,文章前段討論簡單工廠模式(SimpleFactory)和工廠模式(FactoryPattern)時,工廠生產(chǎn)的產(chǎn)品是Vehicle,現(xiàn)在為了引申出抽象工廠模式(AbstractFactory)將Vehicle的子類Car進行了細分,工廠生產(chǎn)產(chǎn)品關注點不在是Vehicle,而是著重討論生產(chǎn)Car相關配件 - Engine, Door和Battery。

抽象工廠模式

在進一步探討抽象工廠模式之前,我們有必要了解“產(chǎn)品族”和“產(chǎn)品等級”這兩個概念,所謂產(chǎn)品族,是指位于不同產(chǎn)品等級結構中,功能相關聯(lián)的產(chǎn)品組成的家族。比如奔馳的Engine, Door, Battery組成一個產(chǎn)品族,Audi的Engine, Door, Battery組成一個產(chǎn)品族。而這兩個產(chǎn)品族都來自于三個產(chǎn)品等級:Engine, Door和Battery。

生產(chǎn)奔馳的Engine, Door和Battery必須匹配才能組合成有效使用的BenzCar;同樣生產(chǎn)Audi的Engine, Door和Battery也必須匹配才能組合成可用的AudiCar。也許讓同一個工廠生產(chǎn)這些配件才能保證合理匹配,例如讓BenzFactory生產(chǎn)奔馳的Engine, Door和Battery,讓AudiFactory生產(chǎn)Audi的Engine, Door和Battery,采用這樣的做法,我們也避免了工廠模式中為每一種類型的配件創(chuàng)建對應的工廠。既然BenzFactory和AudiFactory都是為了創(chuàng)建一個產(chǎn)品族,這樣我們可以將BenzFactory和AudiFactory抽象出一個AbstractFactory,該抽象工廠有三個方法,-createEngine, -createDoor, -createBattery,

如下代碼所示,使用了抽象工廠模式實現(xiàn)了生產(chǎn)Car的配件,

class Component { // 配件 - 翻譯不好見諒哈
    var price: Float = 0.0
    
}

class Engine: Component {
    var speed: Int = 0
    var weight: Float = 0.0
}

class BenzEngine: Engine {
    
}

class AudiEngine: Engine {
    
}

class Door: Component {
    var color: UIColor = UIColor.clearColor()
    var hasWindow: Bool = true
}

class BenzDoor: Door {

}

class AudiDoor: Door {

}


class Battery: Component {
    var workTime: Int = 0
    var lekeage: Bool = false //是否漏電
}

class BenzBattery: Battery {
    
}

class AudiBattery: Battery {
    
}

protocol AbstractCarFactory {
    func createEngine() -> Engine
    func createDoor() -> Door
    func createBattery() -> Battery
}

class BenzFactory: AbstractCarFactory {
    func createEngine() -> Engine {
        return BenzEngine()
    }
    
    func createDoor() -> Door {
        return BenzDoor()
    }
    
    func createBattery() -> Battery {
        return BenzBattery()
    }
}

class AudiFactory: AbstractCarFactory {
    func createEngine() -> Engine {
        return AudiEngine()
    }
    
    func createDoor() -> Door {
        return AudiDoor()
    }
    
    func createBattery() -> Battery {
        return AudiBattery()
    }
}

簡單描述上面的代碼,

  1. 定義了一個基類Component,它代表一個汽車配件的模型,它有一個Float類型的price屬性;
  2. 定義三個子類Engine, Door, Battery,它們都繼承自Component,并且各自定自己的屬性,例如Battery子類定義了workTime(電池工作時長)和lekeage(是否漏電),它們屬于抽象產(chǎn)品(abstract product)的范疇;
  3. 定義了Benz和Audi兩種車型Engine, Door, Battery的子類,分別是BenzEngine, BenzDoor, BenzBattery和AudiEngine, AudiDoor, AudiBattery,它屬于具體產(chǎn)品(concrete product)的范疇;
  4. 定義核心角色AbstractCarFactory,它是一個Protocol,它內部定義了-crateEngine, -createDoor, -createBattery三個方法,分別用于創(chuàng)建Engine, Door, Battery抽象產(chǎn)品,抽象工廠并不創(chuàng)建具體的產(chǎn)品;
  5. 定義BenzFactory和AudiFactory兩個具體工廠,它們實現(xiàn)AbstractCarFactory協(xié)議,負責創(chuàng)建具體的產(chǎn)品對象,也就是說抽象工廠定義接口,具體工廠實現(xiàn)接口邏輯。

分析總結,抽象工廠模式與工廠模式一樣,都包含如下4個角色,即,

  • 抽象工廠
  • 具體工廠
  • 抽象產(chǎn)品
  • 具體產(chǎn)品

區(qū)別在于,工廠模式只生產(chǎn)一種單一的產(chǎn)品,而抽象工廠模式生產(chǎn)多種類型的產(chǎn)品,準確來說,抽象工廠模式可以生產(chǎn)一個產(chǎn)品族。

它們之間的關系如下圖UML所示,

AbstractFactory.jpg

此時我們可以這樣創(chuàng)建一個Benz汽車對象,如下代碼所示,

class Car {
    var engine: Engine
    var door: Door
    var battery: Battery
    
    init(engine: Engine, door: Door, battery: Battery) {
        self.engine = engine
        self.door = door
        self.battery = battery
    }
}


func makeCar(carFactory: AbstractCarFactory) -> Car {
    let engine = carFactory.createEngine()
    let door = carFactory.createDoor()
    let battery = carFactory.createBattery()
    let car = Car(engine: engine, door: door, battery: battery)
    return car
}

let benzCar = makeCar(BenzFactory())

簡單解釋上述代碼,首先新定義了一個類Car,它包含Engine, Door, Battery三個屬性,并定義了-init初始化方法;接著定義了-makeCar方法,它接受的參數(shù)是AbstractCarFactory,返回一個Car對象;我們需要創(chuàng)建benzCar,直接為-makeCar方法參數(shù)設置為BenzFactory對象即可。

筆者冒泡:本文在兩臺不同電腦寫作,因為系統(tǒng)原因,Mac Book Pro不能使用ArgoUML,所以只能換個方式畫UML類圖,所以導致抽象工廠模式的UML圖和之前的畫風不一樣。因為時間有限,所以也重新畫圖,導致風格不統(tǒng)一,忘讀者見諒。

Objective-C SDK中的抽象工廠模式

在Objecitve-C SDK中,NSNumber是我們經(jīng)常使用的類,我們可以使用NSNumber創(chuàng)建不同類型的數(shù)據(jù),如下代碼,

NSNumber *integerValue = [NSNumber numberWithInteger:1000];
NSNumber *boolValue = [NSNumber numberWithBool: YES];
...

實際上NSnumber是一個類族,大概就是文章前面提到的產(chǎn)品族這樣的概念吧,它包含Char, NSInteger, Bool, Short...等,如果沒有NSNumber提供的抽象工廠模式來創(chuàng)建對象,那么對于我們編碼來說簡直就是噩夢。

NSNumber本身就是一個抽象工廠,它提供了不同的接口用于創(chuàng)建不同類型數(shù)據(jù)對應的對象,至于創(chuàng)建對象的細節(jié),則交付給實現(xiàn)NSNumber抽象接口的具體工廠。

微信公眾號

歡迎關注本人微信公眾號,請掃描下方二維碼,

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容