為了API的易用性、易維護性和健壯性,蘋果工程師在iOS系統(tǒng)框架中其實運用了不少經(jīng)典設計模式,而這些實踐也正是因為良好的封裝性,開發(fā)中我們雖日日相對,卻也難以察覺它的存在。相對于其他生搬硬造隔靴搔癢的例子,這些我們熟悉的不能再熟悉的API方是學習設計模式的最佳案例。因此本系列擬以iOS系統(tǒng)框架中相關(guān)設計模式的實踐為起點,探討研究23種經(jīng)典設計模式。
本文先講述《創(chuàng)建型設計模式》(Creational Patterns)。
創(chuàng)建型設計模式是在創(chuàng)建一個類時用到的設計模式,總共有5種,其中工廠方法模式還可以根據(jù)實現(xiàn)的不同,分出簡單工廠模式和工廠方法模式。
簡單工廠模式(Factory Method) iOS系統(tǒng)Foundation
框架中的NSNumber
所應用的就是簡單工廠模式。
簡單工廠模式主要解決不同情況下,需要創(chuàng)建不同子類,而這些子類又需要轉(zhuǎn)化為公共父類讓外界去使用的問題,因為這樣對外接口只有一個,實際行為卻因子類的具體實現(xiàn)而不同。拿NSNumber
來說,傳入Int
、Float
、Double
、Char
和UnsignedChar
等具體number
,NSNumber
返回的是對應的NSNumber
子類,而我們使用時只知NSNumber
,不知具體的子類。
1 2 3 4 5 6 7 8 9 import UIKitlet boolValue: Bool = true let doubleValue: Double = 1.0 let boolN = NSNumber (value: boolValue)let doubleN = NSNumber (value: doubleValue)print (type(of:boolN))print (type(of:doubleN))
輸出結(jié)果為
1 2 __NSCFBoolean __NSCFNumber
如果用簡單工廠方法實現(xiàn)NSNumber
(為了不與系統(tǒng)的NSNumber
混淆,本文自己定義的NSNumber
均去掉NS
前綴,改為Number
),代碼大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 // 抽象產(chǎn)品 protocol Number { func doubleValue () -> Double func boolValue () -> Bool } // 生產(chǎn)工廠 class NumberFactory { func createNumber (value: Bool) -> Number { return __NSCFBoolean(value: value) } func createNumber (value: Double) -> Number { return __CFNumber(value: value) } } // 具體的產(chǎn)品A private class __CFBoolean : Number { let bool: Bool init (value: Bool ) { bool = value } func doubleValue () -> Double { return bool ? 1 : 0 } func boolValue () -> Bool { return bool } } // 具體的產(chǎn)品B private class __CFNumber : Number { let double: Double init (value: Double ) { double = value } func doubleValue () -> Double { return double } func boolValue () -> Bool { return double != 0 } }
其中Number
是抽象協(xié)議,負責定義行為,而__CFNumber
和__CFBoolean
是實現(xiàn)了Number
抽象協(xié)議的私有實體類,NumberFactory
則是一個創(chuàng)建Number
的工廠。
具體使用時,先創(chuàng)建工廠,然后根據(jù)需要創(chuàng)建具體的實體類:
1 2 3 4 5 // 先創(chuàng)建工廠 let factory = NumberFactory ()// 然后根據(jù)需要創(chuàng)建實體類 let boolN = factory.createNumber(value: false )let doubleN = factory.createNumber(value: 2.0 )
而由于Objective-C
的初始化方法中可以直接返回子類型,因此不必創(chuàng)建一個單獨的工廠類NumberFactory
,直接將相應的工廠方法邏輯封裝在NSNumber
的init
方法中即可:
1 2 3 4 5 6 7 8 @implementation NSNumber - (NSNumber *)initWithBool:(BOOL )value { return [[__NSCFBoolean alloc] initWithBool:value]; } - (NSNumber *)initWithDouble:(double)value { return [[__NSCFNumber alloc] initWithDouble:value]; } @end
而在Swift
中不可能從init
初始化方法中返回一個子類。(Swift
的init
方法除了return nil
外不能有返回值)
工廠方法模式(Factory Method) 簡單工廠模式中,工廠只有一個實體類NumberFactory
,每當添加新的產(chǎn)品(即新實現(xiàn)Number
協(xié)議的子類),都需要去修改這個工廠。 比如上文新添加一個針對Float
實現(xiàn)Number
協(xié)議的__CFFloat
(系統(tǒng)中的NSNumber
并沒有實體子類__NSCFFloat
,而是所有的數(shù)字類型都封裝為__NSCFNumber
),
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 新添加的具體的產(chǎn)品C private class __CFFloat : Number { let float: Float init (value: Float ) { float = value } func doubleValue () -> Double { return Double (float) } func boolValue () -> Bool { return float != 0 } }
那么NumberFactory
也需要改動:
1 2 3 4 5 6 7 8 9 10 11 12 13 // 生產(chǎn)工廠 class NumberFactory { func createNumber (value: Bool) -> Number { return __NSCFBoolean(value: value) } func createNumber (value: Double) -> Number { return __CFNumber(value: value) } // 新添加的工廠方法 func createNumber (value: Float) -> Number { return __CFFloat(value: value) } }
為解決這個弊端,可以將工廠NumberFactory
也抽象一層,定義為一個協(xié)議:
1 2 3 4 // 抽象工廠 protocol NumberFactory { func createNumber (value: Any) -> Number }
然后針對不同的Number
實體子類,都定義相應的工廠NumberFactory
子類即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Bool 專用的工廠類 class BoolNumberFactory : NumberFactory { func createNumber (value: Any) -> Number { guard let value = value as ? Bool else { fatalError ("value must be a bool" ) } return __CFBoolean(value) } } // Double 專用的工廠類 class DoubleNumberFactory : NumberFactory { func createNumber (value: Any) -> Number { guard let value = value as ? Double else { fatalError ("value must be a double" ) } return __CFNumber(value) } }
具體使用中,先創(chuàng)建工廠,然后直接根據(jù)相應的工廠創(chuàng)建相應的Number
:
1 2 3 4 5 6 7 // 先創(chuàng)建工廠 let boolFactory = BoolNumberFactory ()let doubleFactory = DoubleNumberFactory ()// 然后直接根據(jù)相應的工廠創(chuàng)建相應的Number let boolN = boolFactory.createNumber(value: false )let doubleN = doubleFactory.createNumber(value: 2.0 )
如果想新添加一個針對Float
實現(xiàn)Number
協(xié)議的__CFFloat
,添加完成后,直接再添加一個對應的NumberFactory
子類即可。
1 2 3 4 5 6 7 8 9 // Float 專用的工廠類 class FloatNumberFactory : NumberFactory { func createNumber (value: Any) -> Number { guard let value = value as ? Float else { fatalError ("value must be a float" ) } return __CFFloat(value) } }
這就是工廠方法模式與簡單工廠模式的區(qū)別,即工廠方法模式不但抽象了產(chǎn)品,而且抽象了工廠。
抽象工廠模式(Abstract Factory) 工廠方法模式抽象了工廠,但只負責生產(chǎn)一種產(chǎn)品。抽象工廠模式與工廠方法模式一般無二,都是抽象了工廠和產(chǎn)品,只是抽象工廠模式中的抽象工廠會負責生產(chǎn)一種以上相關(guān)聯(lián)、會一起使用的產(chǎn)品。 還是以Number
的抽象工廠NumberFactory
舉例。Foundation
中類似NSNumber
類簇的,還有NSArray
:
1 2 3 4 5 6 7 8 import UIKitlet array0 = NSArray (array: [])let array1 = NSArray (arrayLiteral: 1 , 2 )let array2 = NSArray (arrayLiteral: 1 )print (type(of:array0))print (type(of:array1))print (type(of:array2))
打印結(jié)果如下:
1 2 3 __NSArray0 __NSArrayI __NSSingleObjectArrayI
定義NSArray
的抽象協(xié)議并實現(xiàn)兩個私有類__CArray0
和__CArrayI
,為不與系統(tǒng)中的NSArray
和Array
混淆,這里取CArray
:
1 2 3 4 5 6 7 8 9 10 11 12 13 protocol CArray { var count : Int { get } // 其他公共接口 } private class __CArray0 : CArray { var count = 0 // 其他公共接口實現(xiàn) } private class __CArrayI : CArray { var count = 0 // 其他公共接口實現(xiàn) }
定義Number
和CArray
的抽象工廠協(xié)議NumberAndArrayFactory
:
1 2 3 4 5 6 protocol NumberAndArrayFactory { // 用來生產(chǎn)Number的工廠方法 func createNumber (value: Any) -> Number // 用來生產(chǎn)CArray的工廠方法 func createCArray () -> CArray }
定義抽象工廠NumberAndArrayFactory
的具體實現(xiàn)類BoolNumberAndArray0Factory
:
1 2 3 4 5 6 7 8 9 10 11 12 class BoolNumberAndArray0Factory : NumberAndArrayFactory { func createNumber (value: Any) -> Number { guard let value = value as ? Bool else { fatalError ("value must be a bool" ) } return __CFBoolean(value) } func createCArray () -> CArray { return __CArray0() } }
具體使用時,先創(chuàng)建抽象工廠NumberAndArrayFactory
的具體實現(xiàn)類,然后在調(diào)用這個實現(xiàn)類上的工廠方法,創(chuàng)建相應的產(chǎn)品Number
或者CArray
:
1 2 3 4 5 6 // 創(chuàng)建抽象工廠`NumberAndArrayFactory`的具體實現(xiàn)類 let boolNumberAndArray0Factory = BoolNumberAndArray0Factory ()// 調(diào)用工廠方法,創(chuàng)建相應的產(chǎn)品 let boolNumber = boolNumberAndArray0Factory.createNumber(true )let 0CArray = boolNumberAndArray0Factory.createCArray()
需要注意的是,這里為了說明抽象工廠模式,抽象工廠NumberAndArrayFactory
所創(chuàng)建的Number
和CArray
沒有任何關(guān)聯(lián),在實際項目中,同一抽象工廠所創(chuàng)建的產(chǎn)品是關(guān)聯(lián)的,一般是一起結(jié)合使用,如果不關(guān)聯(lián),也不必用抽象工廠模式。
建造者模式(builder) 建造者模式是用來隔離復雜對象的配置過程,將復雜對象的配置過程單獨封裝成一個builder
對象,完成配置后,再獲取配置完成的實例對象。
cocoa中使用建造者模式的類是NSDateComponents
,
1 2 3 4 5 6 7 8 9 10 11 12 import Foundationvar builder = NSDateComponents ()builder.hour = 10 builder.day = 6 builder.month = 9 builder.year = 1940 builder.calendar = Calendar (identifier: .gregorian) var date = builder.dateprint (date!)
輸出結(jié)果為:
1 1940 -09 -06 01 :00 :00 +0000
NSDateComponents
相當于日期的一個builder
,NSDateComponents
用來配置日期的各個部分,配置完成后,最終獲取對應的Date
日期。NSDateComponents
的實現(xiàn)大致如下(為避免與系統(tǒng)中的NSDateComponents
和DateComponents
混淆,這里取DateBuilder
):
1 2 3 4 5 6 7 8 9 10 11 12 13 class DateBuilder { var hour = 0 var day = 0 var month = 0 var year = 1970 var calendar = Calendar (identifier: .gregorian) var date: Date { // 根據(jù)date components計算日期,比較復雜,這里省略了計算過程 let calculatedDate = ... return calculatedDate } }
但這使用上不能鏈式調(diào)用,很不方便,加上各個屬性的設置方法,返回自己本身,可以實現(xiàn)鏈式調(diào)用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class DateBuilder { var hour = 0 var day = 0 var month = 0 var year = 1970 var calendar = Calendar (identifier: .gregorian) var date: Date { // 根據(jù)DateComponents計算日期,比較復雜,這里省略了計算過程 let calculatedDate = ... return calculatedDate } func hour (_ hour: Int) -> DateBuilder { self .hour = hour return self } func day (_ day: Int) -> DateBuilder { self .day = day return self } func month (_ month: Int) -> DateBuilder { self .month = month return self } func year (_ year: Int) -> DateBuilder { self .year = year return self } func calendar (_ calendar: Calendar) -> DateBuilder { self .calendar = calendar return self } }
使用時很方便:
1 let date = DateBuilder ().hour(1 ).day(2 ).month(12 ).year(2017 ).date
原型模式(Prototype) 原型模式其實就是一個類能夠通過自身copy,創(chuàng)建一個內(nèi)容一模一樣的新實例,這在iOS的系統(tǒng)框架Foundation
中挺常見的,NSString
、NSArray
、NSDictionary
和NSParagraphStyle
的 copy
、mutableCopy
方法都能復制一個新的實例,從而免去了從零創(chuàng)建一個復雜類的麻煩。 如NSParagraphStyle
,當獲取到一個paragraphStyle
之后,突然又想在其基礎(chǔ)上改動同時又不想直接改變原來NSParagraphStyle
,最方便的不過copy
一份原來的,然后在改動。
1 2 3 4 let paragraphStyle = NSParagraphStyle .default let mutablePara = paragraphStyle.mutableCopy() as ! NSMutableParagraphStyle mutablePara.lineSpacing = 10 mutablePara.paragraphSpacing = 5
如果想實現(xiàn)原型模式,在swift中直接實現(xiàn)NSCopying
和NSMutableCopying
協(xié)議即可。
單例模式(Singleton) 單例模式即一個類至始至終只有一個實例(單例類是可以新創(chuàng)建實例的,但一般都會用公共的那個單例實例),常用于Manager上。單例在iOS系統(tǒng)中十分常用,如NSParagraphStyle.default
、 UIScreen.main
、 UIApplication.shared
、 UserDefaults.standard
和FileManager.default
等都是單例。
1 2 3 4 5 let paragraphStyle = NSParagraphStyle .default let screen = UIScreen .mainlet application = UIApplication .sharedlet userDefault = UserDefaults .standardlet fileManager = FileManager .default
在swift中實現(xiàn)一個單例模式,也是非常簡單的。
1 2 3 4 5 6 7 8 class Manager { // 單例 static let shared = Manager () // 私有化后,這個對象只會有單例這一個實例 private init () { } }
上述單例,初始化方法私有化了,因此在整個APP的生命周期中,將只有一個此類的實例,即單例。 但有時,單利只是給一個默認配置而已,如果想自定義,可以完全重新初始化一個新的實例,如
1 2 3 4 5 6 7 8 class ParagraphStyle { // 單例 static let default = ParagraphStyle () // 沒有私有化,這個對象如果有需要可以創(chuàng)建單例以外的新的實例 init () { } }
總結(jié) 創(chuàng)建型設計模式在iOS系統(tǒng)中的運用相當廣泛,而我們開發(fā)中只要有一定的抽象,基本都會用到,尤其是簡單工廠模式和工廠模式、單例模式,希望本文的講解能讓大家能真正理解這些開發(fā)模式,并在開發(fā)中順利應用。
參考文章:
Class Clusters 從NSArray看類簇 創(chuàng)建者模式-建造者模式(The Builder Pattern) 簡單工廠模式(Simple Factory Pattern) 工廠方法模式(Factory Method Pattern) 抽象工廠模式(Abstract Factory) Swift中編寫單例的正確方式