前言
工廠模式應(yīng)用非常廣泛,經(jīng)常可以在一些復(fù)雜的應(yīng)用或框架上看見其影子.正所謂
沒見過豬跑,但我吃過豬肉.
你可以在NSString看到工廠方法.嘗試執(zhí)行以下代碼:
NSString *str = @"string 1";
NSLog(@"string 1 class : %@", [str class]);
NSString *str2 = [NSString stringWithUTF8String:"string 2"];
NSLog(@"string 2 class :%@", [str2 class]);
可以在控制臺中看到以下日志:
string 1 class : __NSCFConstantString
string 2 class :NSTaggedPointerString
很明顯,在分別調(diào)用不同的類方法生成了兩個繼承NSString的子類.在OC中,如果你看到一個類有著豐富多樣的類方法來生成對象,那么它就有可能使用工廠方法.在上一次面試中,面試官問到了這個問題.我發(fā)現(xiàn)自己表達(dá)得很爛.as we know:
如果你不能將知識通過簡潔的語言表達(dá)出來,那說明你還沒掌握這個知識.
分類
一般來說,分為三個類:簡單工廠,工廠方法,抽象工廠.由于簡單工廠和工廠方法類似,就放在本篇講,而工廠方法在下一篇介紹.
他們在四人幫中(Gang of four)的定義如下:
- 簡單工廠 -- 定義一個用于創(chuàng)建對象的沒有暴露的接口,讓子類決定哪一個類.
- 工廠方法 -- 定義一個用于創(chuàng)建對象的接口,讓子類決定哪一個類.Factory Method使一個類實(shí)例化延遲到其子類.
- 抽象工廠 -- 提供一個創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而無需指定他們具體的類.
注:'讓子類決定'的意思是在編碼時無法確定具體實(shí)例化哪一個類,需在運(yùn)行時(runtime)通過交互或者通過參數(shù)決定實(shí)例化哪一個類.
一個例子
假如現(xiàn)在我們有這么一個需求:
公司正在做一個商城APP叫Bao Si APP,簡稱BS.在BS中,當(dāng)用戶未完善個人信息的時候,下單需要跳轉(zhuǎn)到完善個人信息界面中完善個人信息.
這個信息界面是一個表單,包括姓名地址,性別,手機(jī)號碼,當(dāng)前銀行卡之類.這個應(yīng)該是比較簡單的,所以你就開始動手寫了.當(dāng)你寫到一半的時候,PM跑過來跟你說:"之后我們繼續(xù)討論了一下,你把這個表單改為動態(tài)的.現(xiàn)在的表單比較簡單,以后我們需要添加其他的表單信息,比如說生日.有些信息可能變得很重要,所以這個表單是由后臺的運(yùn)營人員控制的.只要后臺一更改,前臺就得跟著改變."
這時候,你應(yīng)該放下你手中的刀,并坐下來聽一首<The Wolven Storm>,慢慢思考人生.
我們可以將上述的需求總結(jié):
- 動態(tài)性.客戶端的配置應(yīng)該由后臺配置,我們通過這個配置來生成對應(yīng)的表單.每一個表單的控件類型都由一個唯一標(biāo)識符
type
控制.讓type
決定創(chuàng)建對應(yīng)的控件對象.即我們不知道我們要創(chuàng)建的具體的類. - 多態(tài)性.每一個表單控件中都有相同的標(biāo)題,以及類似的方法:
getValue
,isValidate
(為了簡單起見,假設(shè)這里就只有兩個方法).每個控件的getValue
方法又都不同.對于地址,需要返回Label.text
中的值.對于日期,則需要textfield.text
. 將一些責(zé)任(方法)委托給子類實(shí)現(xiàn).
基本上符合這兩個點(diǎn)的時候需求的時候,就能使用簡單工廠模式了.
簡單工廠模式
按照工廠模式,因?yàn)槲覀儾恢浪獎?chuàng)建的具體的類,所以希望通過一個工廠類來幫我們生成對應(yīng)的產(chǎn)品類,所以在客戶端中調(diào)用工廠方法中的類方法controlWithType:
并傳入一個參數(shù).這時候就需要知道具體什么方法,只關(guān)心從控件中取出來的值.
于是可以得到以下的UML結(jié)構(gòu)圖.
注:在當(dāng)前的需求中,每一個控件都有一個公共屬性titleLabel
,這個屬性明顯不屬于抽象產(chǎn)品類.所以為了靈活起見,定義了一個基類繼承抽象產(chǎn)品類ControlProtocol
.這樣,子類也就有了titleLabel
,子類也能繼承對應(yīng)的方法.
簡單工廠有兩種方法實(shí)現(xiàn):
-
參數(shù)化方法. 即在工廠類里面寫一個帶type類型的生產(chǎn)方法,在這個方法中寫判斷邏輯.
@interface AKFactory : NSObject - (AKBasicControl *)controlWithType:(NSString *)type; @end
-
為每一個產(chǎn)品類寫一個生產(chǎn)方法.這種方法將原本的判斷邏輯延遲到了客戶端實(shí)現(xiàn).
@interface AKFactory : NSObject - (AKBasicControl *)TextField; - (AKBasicControl *)Button; @end
當(dāng)我們從后臺獲取配置的時候,就可以使用工廠方法生成我們所需要的控件.在這個例子中,我們使用的是靜態(tài)配置來生成一個動態(tài)表單.客戶端通過調(diào)用工廠方法生成相應(yīng)的控件并展示在動態(tài)表單上面.這里我們不需要知道他們具體的類型是什么,只需要按照要求檢查數(shù)據(jù)的合法性與獲取數(shù)據(jù)即可.
詳細(xì)的代碼我已經(jīng)托管到了github上面:FactoryMethodExample.
工廠方法模式
當(dāng)我們寫好并投入使用時.果然,PM又跑過來說:"這個表單的控件種類太少了吧.再添加個開關(guān)控件,就加一個而已,很簡單的".你聽了之后內(nèi)心毫無波動,甚至還想笑.好吧,看個蘿莉思考一下.
首先,很容易想到一個解決方案,那就是修改AKFactory
類的類方法controlWithType:
, 在這個方法中添加如類似一下的代碼:
if ([type isEqualToString:@"radio"]) {
return [[AKRadio alloc] init];
}
這樣子確實(shí)可以快速解決問題.不過這也太low了,我們學(xué)習(xí)設(shè)計模式除了要解決一些復(fù)雜問題之外,還有一個目的就是裝逼.這樣子寫會被高手鄙視的.但是話說回來.這樣子寫其實(shí)是有問題的,因?yàn)楫?dāng)一個類完成之后,在不到萬不得已的情況下,我們不應(yīng)該去修改它.這就是開閉原則(Open/closed principle).
Software entities should be open for extension,but closed for modification. -- Object Oriented Software Construction
翻譯過來就是:“軟件實(shí)體應(yīng)當(dāng)對擴(kuò)展開放区转,對修改關(guān)閉."
對運(yùn)行良好的類,我們不應(yīng)該去修改它.在改變代碼之后,不僅維護(hù)變得更復(fù)雜了,而且無法知道之前的功能是否會失效.所以干脆禁止修改,以免觸發(fā)隱藏Bug.所以我們需要將原來的簡單工廠模式代碼,并使其容易擴(kuò)展.
由于需要擴(kuò)展,那我們將不能委托AKFactory
生成產(chǎn)品,而是通過繼承抽象(協(xié)議)工廠中的構(gòu)造方法來為每一個產(chǎn)品類定制單獨(dú)的構(gòu)造方法.
可以得到以下UML圖:
繼承
AKFactory
并重載+ makeControl
得到AKDateFactory
和AKTextFieldFactory
.在客戶端調(diào)用其具體工廠類生成對應(yīng)的對象.例如:在客戶端調(diào)用AKTextFieldFactory
類中的+ makeControl
生成AKLabel
類.但是,不像簡單工廠,判斷控件類型的邏輯需要我們在客戶端實(shí)現(xiàn).現(xiàn)在,我們要擴(kuò)展一個新的控件就簡單多了.只不要繼承
AKFactory
為AKSwitchFactory
,并重載+ makeControl
.然后,只需要在客戶端中使用就行了.這樣既不修改原來的類,也能擴(kuò)展新功能.終于可以好好裝逼了.具體代碼在FactoryMethodExample的factory method分支下.
總結(jié)
當(dāng)無法確定需要創(chuàng)建的具體的類以及這些類具有相似的方法時,就能使用簡單工廠模式或工廠方法模式.工廠方法定義了一個接口,并讓子類決定生成哪個子類,將類的實(shí)例化延遲到子類中,解決編程中無法確定具體類的問題.
簡單工廠與工廠方法之間的區(qū)別是將原來的判斷邏輯轉(zhuǎn)移到轉(zhuǎn)移到其他文件實(shí)現(xiàn). 而產(chǎn)品類卻都是一樣.在實(shí)際的項(xiàng)目中,假設(shè)只需要一些靜態(tài)的幾個產(chǎn)品類,如VIP與非VIP兩種情況,使用簡單工廠即可.但假如遇到當(dāng)前這個例子這用需求,就應(yīng)該使用工廠方法模式.
此外,如果需求更加復(fù)雜,可能需要用到抽象工廠,請看我下一篇文章設(shè)計模式:抽象工廠--Objective-C實(shí)現(xiàn)