
工廠模式概述
工廠模式是設計模式的一種,從功能上來說,它的主要作用是創(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圖所示,

通過使用簡單工廠方法,為我們創(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所示,

與簡單工廠模式相比,工廠模式再也不必煩惱于臃腫的代碼,如果需要制造新的機車類型,比如現(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()
}
}
簡單描述上面的代碼,
- 定義了一個基類Component,它代表一個汽車配件的模型,它有一個Float類型的price屬性;
- 定義三個子類Engine, Door, Battery,它們都繼承自Component,并且各自定自己的屬性,例如Battery子類定義了workTime(電池工作時長)和lekeage(是否漏電),它們屬于抽象產(chǎn)品(abstract product)的范疇;
- 定義了Benz和Audi兩種車型Engine, Door, Battery的子類,分別是BenzEngine, BenzDoor, BenzBattery和AudiEngine, AudiDoor, AudiBattery,它屬于具體產(chǎn)品(concrete product)的范疇;
- 定義核心角色AbstractCarFactory,它是一個Protocol,它內部定義了-crateEngine, -createDoor, -createBattery三個方法,分別用于創(chuàng)建Engine, Door, Battery抽象產(chǎn)品,抽象工廠并不創(chuàng)建具體的產(chǎn)品;
- 定義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所示,

此時我們可以這樣創(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抽象接口的具體工廠。
微信公眾號
歡迎關注本人微信公眾號,請掃描下方二維碼,
