設(shè)計模式:工廠方法--Objective-C實(shí)現(xiàn)

前言

工廠模式應(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é):

  1. 動態(tài)性.客戶端的配置應(yīng)該由后臺配置,我們通過這個配置來生成對應(yīng)的表單.每一個表單的控件類型都由一個唯一標(biāo)識符type控制.讓type決定創(chuàng)建對應(yīng)的控件對象.即我們不知道我們要創(chuàng)建的具體的類.
  2. 多態(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)圖.

simple factory
simple factory

注:在當(dāng)前的需求中,每一個控件都有一個公共屬性titleLabel,這個屬性明顯不屬于抽象產(chǎn)品類.所以為了靈活起見,定義了一個基類繼承抽象產(chǎn)品類ControlProtocol.這樣,子類也就有了titleLabel,子類也能繼承對應(yīng)的方法.

簡單工廠有兩種方法實(shí)現(xiàn):

  1. 參數(shù)化方法. 即在工廠類里面寫一個帶type類型的生產(chǎn)方法,在這個方法中寫判斷邏輯.

    @interface AKFactory : NSObject
    - (AKBasicControl *)controlWithType:(NSString *)type;
    @end
    
  2. 為每一個產(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)心毫無波動,甚至還想笑.好吧,看個蘿莉思考一下.


the loli
the loli

首先,很容易想到一個解決方案,那就是修改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圖:

Factory method
Factory method

繼承AKFactory并重載+ makeControl得到AKDateFactoryAKTextFieldFactory.在客戶端調(diào)用其具體工廠類生成對應(yīng)的對象.例如:在客戶端調(diào)用AKTextFieldFactory類中的+ makeControl生成AKLabel類.但是,不像簡單工廠,判斷控件類型的邏輯需要我們在客戶端實(shí)現(xiàn).
現(xiàn)在,我們要擴(kuò)展一個新的控件就簡單多了.只不要繼承AKFactoryAKSwitchFactory,并重載+ makeControl.然后,只需要在客戶端中使用就行了.這樣既不修改原來的類,也能擴(kuò)展新功能.終于可以好好裝逼了.
具體代碼在FactoryMethodExamplefactory 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)

引用文章

Factory method pattern

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末对粪,一起剝皮案震驚了整個濱河市剔交,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖松靡,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狐榔,死亡現(xiàn)場離奇詭異坛增,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)薄腻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門收捣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人庵楷,你說我怎么就攤上這事罢艾。” “怎么了尽纽?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵咐蚯,是天一觀的道長。 經(jīng)常有香客問我弄贿,道長春锋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任差凹,我火速辦了婚禮看疙,結(jié)果婚禮上豆拨,老公的妹妹穿的比我還像新娘。我一直安慰自己能庆,他們只是感情好施禾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著搁胆,像睡著了一般弥搞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渠旁,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天攀例,我揣著相機(jī)與錄音,去河邊找鬼顾腊。 笑死粤铭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的杂靶。 我是一名探鬼主播梆惯,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吗垮!你這毒婦竟也來了垛吗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烁登,失蹤者是張志新(化名)和其女友劉穎怯屉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饵沧,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锨络,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了狼牺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羡儿。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锁右,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讶泰,我是刑警寧澤咏瑟,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站痪署,受9級特大地震影響码泞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜狼犯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一余寥、第九天 我趴在偏房一處隱蔽的房頂上張望领铐。 院中可真熱鬧,春花似錦宋舷、人聲如沸绪撵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽音诈。三九已至,卻和暖如春绎狭,著一層夾襖步出監(jiān)牢的瞬間细溅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工儡嘶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喇聊,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓蹦狂,卻偏偏與公主長得像誓篱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鸥咖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,139評論 25 707
  • 設(shè)計模式基本原則 開放-封閉原則(OCP)燕鸽,是說軟件實(shí)體(類、模塊啼辣、函數(shù)等等)應(yīng)該可以拓展啊研,但是不可修改。開-閉原...
    西山薄涼閱讀 3,798評論 3 14
  • 設(shè)計模式匯總 一鸥拧、基礎(chǔ)知識 1. 設(shè)計模式概述 定義:設(shè)計模式(Design Pattern)是一套被反復(fù)使用党远、多...
    MinoyJet閱讀 3,947評論 1 15
  • Iterator模式 (迭代器) 一個一個遍歷 一個集合類可以遵守 Iterator 協(xié)議,并實(shí)現(xiàn)一個 Itera...
    SSBun閱讀 1,843評論 0 15
  • 01 林夢突然在朋友圈更新了一條動態(tài):“我們的五年,敵不過你和她的五個月腕柜〖盟疲”文字后面加上了3個心碎的表情,表明自己...
    洋芋絲絲閱讀 4,071評論 71 73