從手機(jī)制造談設(shè)計模式(上)

引言:本文并不是從代碼角度上來談設(shè)計模式仔引,而是希望類比手機(jī)制造嘗試以一條線來從常用設(shè)計模式的使用場景來介紹設(shè)計模式的(畢竟設(shè)計模式是面向?qū)ο蟮模┧枷牒褪褂梅椒ǎ瑫r我在每個設(shè)計模式場景的介紹中也會提供類圖幫助大家理解褐奥,如果需要在代碼層次有更多的實踐可以參考《Head First設(shè)計模式》


本文中我會提到以下設(shè)計模式:

First:手機(jī)制造中的策略模式

大家都知道每個手機(jī)制造廠家都有很多機(jī)型咖耘,每一款機(jī)型的每個部件往往都是不一樣的,我們拿攝像頭和小米手機(jī)來舉個栗子:小米5的攝像頭是光學(xué)防抖的撬码,小米5s的攝像頭擁有超感光元器件(因此被譽為暗夜之眼)儿倒,而小米6的攝像頭則是變焦雙攝。很明顯我標(biāo)識出來的地方都是這些攝像頭的屬性呜笑,依據(jù)這些屬性每個攝像頭可以完成不同的拍照行為夫否,我們姑且做一個映射:

攝像頭 拍照能力(或行為)
光學(xué)防抖 在手機(jī)抖動的情況下依然可以拍出清晰的照片
超感光元器件 在光線很低的情況下依然可以拍出清晰的照片
變焦雙攝 單反級的景深和背景虛化

那么不用設(shè)計模式,正常的去定義這些手機(jī)用類圖如何表示呢叫胁?
大概如下:

非策略模式.png

很明顯所有的手機(jī)機(jī)型都需要繼承一個基類凰慈,而且所有手機(jī)都具有打電話這個方法且都是一致的,所以把打電話放在基類里面是可以的驼鹅。但是拍照這個方法不可以微谓,因為每個機(jī)型拍照的能力不一樣,如果放在基類里面則都會被繼承输钩,這樣是不對的豺型。所以常見的方法是寫一個接口,每個機(jī)型都去繼承這個接口并各自實現(xiàn)买乃,這樣就可以解決問題触创。但是這又帶來另外一個問題,這個接口實在是被繼承太多次了为牍!而且只是為了一個不同的行為哼绑,假設(shè)還有其他行為如打游戲又需要繼承另一個接口,簡單算個賬碉咆,假設(shè)總共有2300萬部手機(jī)抖韩,5個不同接口,2300萬*5疫铜,很大的一個數(shù)字.....那么沒有辦法把它放在父類里面去繼承嗎茂浮?


答案:是可以的,而且很簡單,“就把它放在父類里面跋俊顽馋!”(感覺自己在找打)實現(xiàn)如下:

策略模式.png

我們按類圖的上下兩部分來說:

  • 封裝行為

    首先我們依然把camera當(dāng)成一個接口,這一點和非策略模式是一致的幌羞,但是我們有了具體實現(xiàn)類寸谜,光學(xué)防抖的camera,超感光元器件的camera,變焦雙攝的camera,并且我們在不同的camera里面實現(xiàn)了自己的takePhoto方法,這樣行為就被封裝了属桦。

  • 動態(tài)設(shè)置行為

    看下半部分熊痴,父類里面多了一個屬性叫camera,其實是引用類聂宾,但是沒有初始化果善,初始化的步驟我們放在了setCamera里面,同樣takePhoto的我們也作為一個方法系谐。
    我們看看子類怎么用巾陕,以小米5為??:
    1.初始化camera
    private void setCamear() { camera = new 光學(xué)防抖的camera();
    }
    2.可以拍照了
    public void useCameraTakePhoto() { print(camera.takePhoto);
    }
    3.拍照結(jié)果:
    log:抖動時的照片依然清晰,具備防抖效果

策略模式的好處是顯而易見的纪他,我們不需要再繼承大量的接口并實現(xiàn)里面的方法惜论,我們只需要在我們需要的時候在子類中去初始化行為就可以,我們甚至可以不初始化camera,誰知道以后拍照需要不要相機(jī)呢止喷?這樣是不是提高了可變性馆类?

Second:手機(jī)制造中的工廠模式(抽象工廠)

我想這個模式放在手機(jī)制造這個例子里面來講大概是最好理解的,我們先看定義(取自《HeadFirst 設(shè)計模式》)

抽象工廠模式提供一個接口弹谁,用于創(chuàng)建相關(guān)或依賴對象的家族乾巧,而不需要明確指定具體類。

回到手機(jī)工廠预愤。顧名思義沟于,手機(jī)工廠一定擁有制造各種手機(jī)的能力,這種能力體現(xiàn)在擁有不同手機(jī)的元器件材料植康,然后再以一種流水線的方式把各種材料組件起來旷太,這樣手機(jī)就誕生了。這里我們需要再細(xì)化一下工作销睁,手機(jī)工廠僅僅提供手機(jī)的各種元器件材料供璧,我們把組裝手機(jī)的工作放在流水線里面,這樣不同的流水線就可以組裝不同的手機(jī)冻记,實踐生產(chǎn)中肯定也是這樣睡毒。我們看一下工廠方法的類圖:

抽象工廠模式.png

這里我們把工廠更具體化了一點(可能實際上只需要一家工廠),我們有兩個工廠冗栗,一個是海淀工廠演顾,一個是朝陽工廠供搀,每個工廠提供不同的元器件。這兩個工廠生產(chǎn)手機(jī)的能力有什么不同呢钠至?答案可以從流水線上看出來葛虐,很明顯海淀工廠可以擁有兩條流水線,一個是小米5s棉钧,一個是小米5sPlus屿脐,僅僅只需要把屏幕的大小換一下(實際上肯定還有其他的元器件,這里只用屏幕大小來區(qū)別)掰盘。然而朝陽工廠只擁有一條流水線即米6。
這個時候我們有一家海淀的線下店缺貨了赞季,這個時候需要“造一些”手機(jī)愧捕,這里我們把用海淀工廠來造手機(jī)(當(dāng)然用朝陽工廠也行,或者兩個工廠用list傳進(jìn)去)申钩。

    protected Phone createPhone(String type) {
        PipeLine pipeLine = null;
        PhoneFactory  factory = new HaiDianFactory();
        if (type.equals("xiaomi5s")) {
           pipeLine = new xiaomi5sPipeLine(factory);
        } else if (type.equals("xiaomi5splus")) {
           pipeLine = new xiaomi5sPlusPipeLine(factory);
        }
        pipeLine.prepare();
        return pipeLine.getPhone();
    }

這樣做的好處是什么次绘?我們解耦了實際的工廠!這是比簡單工廠進(jìn)步的地方撒遣,我們可以根據(jù)需要去初始化工廠接口邮偎,同時根據(jù)所需要的產(chǎn)品類型把實際工廠傳入,這樣就可以制造出不同的產(chǎn)品义黎。如我們線下店的小米5s缺貨啦禾进!我們把海淀工廠當(dāng)做參數(shù)傳給小米5s的流水線,這樣流水線就可以拿到所需要的元器件去準(zhǔn)備生產(chǎn)小米5s廉涕,這樣不是比先判斷是什么手機(jī)我們再一個一個初始化元器件然后再準(zhǔn)備組裝的過程方便的多泻云?
抽象工廠果然英明。

Third:手機(jī)制造中的裝飾者模式

大家都知道每一種機(jī)型都是由各種元器件組成的狐蜕,不同機(jī)型之間有相同的元器件也有不相同的元器件宠纯,我們還是以小米5、小米6舉栗子:

機(jī)型 組成
小米5 camera+fingerPrinter+大猩猩screen
小米6 camera+camera+四曲面screen+fingerPrinter

很明顯可以看出小米6與小米5的不同之處层释,小米5是單攝像頭婆瓜,小米6是雙攝像頭,同時小米5和小米6的屏幕也是不一樣的贡羔。這樣會有什么影響呢廉白?我們從兩個方面談一下:

  • 價格
    很明顯選擇不同的器件組合起來的手機(jī)價格一定是不同,比如有兩款除了攝像頭不同其余器件都相同的機(jī)型乖寒,雙攝一定是比單攝貴的蒙秒;同樣四曲面玻璃一定是比普通玻璃貴的。所以僅有上面幾個器件決定的手機(jī)價格小米6的一定是比小米5高的宵统,這是由成本和工藝決定的(當(dāng)然前提是同一時間段)晕讲。那么價格是如何算的呢覆获?很簡單,組件的價格之和瓢省,A+B+C=總價格弄息,是具有累加屬性的。
  • 描述
    發(fā)燒友都喜歡看手機(jī)的參數(shù)勤婚,比如驍龍835就比驍龍820更吸引人的眼球摹量,所以我們可以這樣描述小米6,這是一款擁有驍龍835cpu``變焦雙攝``四曲面屏幕的旗艦手機(jī)馒胆。當(dāng)然你也可以用同樣的方式去描述米5缨称,這些描述賦予了手機(jī)鮮明的特點。同樣祝迂,這些描述也是可以累加的睦尽,我有這樣一個A+B+C的手機(jī)

好了,廢話扯完了型雳,該扯到設(shè)計模式了当凡,假設(shè)不用設(shè)計模式,我們需要得到一臺機(jī)型的價格和描述我們該如何做纠俭?可能如下:

非裝飾者模式.png

這是一個看上去簡單實際上很傻的設(shè)計沿量,我們把cost實現(xiàn)全部放在具體的機(jī)型里面,這樣的后果是可怕的——假設(shè)攝像頭是從美國采購回來的冤荆,因為匯率問題朴则,價格變化了,我們需要在每個機(jī)型的cost方法里面重新定義價格钓简,就算我們改良一下佛掖,我們把所有的元器件都放在父類里面定義,這樣好嗎涌庭?也不好芥被,這樣所有的機(jī)型都必須繼承這些器件,假設(shè)米5繼承了父類的四曲面screen坐榆,實際上根本用不著拴魄,而且顯然父類是需要維護(hù)的而且維護(hù)的成本不低,這并不是一個父類該做的事情席镀,它應(yīng)該只保留子類共性的部分并提供抽象的方法匹中。好吧,用裝飾者模式改造一下設(shè)計吧:

裝飾者模式.png

我們分成三部分來討論豪诲,被裝飾者顶捷、裝飾者裝飾過程

  • 被裝飾者
    被裝飾者這里指的就是小米5和小米6屎篱,它們同時繼承了手機(jī)這個抽象類并且各種實現(xiàn)了cost方法服赎,但是這樣還夠葵蒂,因為它們僅僅是孤零零的裸機(jī),什么都沒有重虑,沒有攝像頭践付,沒有屏幕也沒有cpu,我們需要裝飾一下它們缺厉。

  • 裝飾者
    我們需要定義一個抽象的decorator來繼承抽象父類phone永高,這樣做是為了繼承父類的抽象方法cost和getDescription。實際的裝飾者是誰提针?是那些元器件命爬,比如攝像頭四曲面玻璃辐脖,驍龍cpu等等饲宛,我們用這些描述累加起來去具體形容一個機(jī)型,同時我們在里面還順便實現(xiàn)一下cost和getDescription方法(如何實現(xiàn)看裝飾過程)揖曾,要注意實際的裝飾者必須關(guān)聯(lián)一個實際的被裝飾者phone(定義在屬性里面)落萎。

  • 裝飾過程
    我們先來裝飾一個小米5
    Phone xiaomi5phone = new 大猩猩screen(new 驍龍cpu(new camera (new fingerPrinter(new xiaomi5()))))
    首先亥啦,這么做可以嗎炭剪?毫無疑問是可以的,因為每個裝飾者的構(gòu)造方法里面都必須傳入一個已經(jīng)初始化過的phone對象翔脱,而且每個裝飾者本身又可以是被裝飾的對象奴拦。這樣就有趣了,我們可以一直裝飾下去届吁。我們可以用用攝像頭裝飾米5错妖,可以描述成有攝像頭的米5,我們可以繼續(xù)用大猩猩屏幕來裝飾有攝像頭的米5疚沐,這樣就可以描述成有大猩猩屏幕且有攝像頭的米5......米6我們?nèi)匀豢梢赃@樣裝飾
    Phone xiaomi6phone = new 四曲面screen(new 驍龍cpu(new camera(new camera(new fingerPrinter(new xiaomi6())))))
    我們甚至可以用camera裝飾兩次讓米6變成雙攝的手機(jī)
    我們再實現(xiàn)一下組件的暂氯,cost和getDescripition方法,以camera為栗子:
    public class Camera extends CondimenDecorator {
    Phone phone;

        public Camera(Phone phone) {
            this.phone = phone;
        }
    
        public String getDescription() {
            return phone.getDescription() + "has 攝像頭";
        }
    
        public double cost() {
            return 20 + phone.cost();
        }
      }
    

很容易看出來無論是描述還是價格都具有累加的屬性,這正是我前面所強調(diào)的地方亮蛔,這樣我們就可以方便的獲取到裝飾后的價格和描述痴施,因為這些屬性均具有累加的性質(zhì),這就是裝飾者模式的三個部分究流。

Fouth:手機(jī)制造中的模板方法模式

前面說過辣吃,手機(jī)在工廠中的加工方法其實就是一條流水線,往往很多手機(jī)的流水線是相似的芬探,僅僅只是修改了流水線中的一兩個步驟神得,這次我們拿小米5標(biāo)準(zhǔn)版和小米5高配版來做個對比,這兩條流水線如下(圖畫的有點歪見諒):

流程圖模擬流水線.png

很明顯偷仿,搞出來兩條流水線是浪費的哩簿,因為小米5標(biāo)準(zhǔn)版和高配版之間的區(qū)別僅僅是內(nèi)存和rom的不同宵蕉,所以我們可以合并一下流程:

合并流水線.png

其實流水線跟程序中的算法是一致,很多時候算法往往是解決一類問題的卡骂,但是每個問題都有自己的條件且條件通常不同国裳,這就需要算法可以動態(tài)的根據(jù)條件來改變算法的步驟,這其實就是模板方法的精髓全跨。


我們先來看看模板方法模式的定義:

模板方法模式在一個方法中定義一個算法的骨架缝左,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下浓若,重新定義算法中的某些步驟渺杉。

我們把上面的流水線用模板方法改造一下:

模板方法模式.png

我們在子類中重新定義流水線的兩個步驟installRAM()installROM,我們再從代碼上看一下父類的流水線這個方法:

final void templatePipeLine() {
    installMainBoard();
    installCPU();
    installRAM();
    installROM();
    hook();
}

這個算法和上面的流水線基本一致,但是還有一點欠缺挪钓,上面的流水線只要判斷是否是高配就決定了流水線的走勢是越,我們這個還是要在子類里面重新實現(xiàn)installRAM()installROM,但是沒有關(guān)系碌上,模板方法還留了一手倚评,我們可以通過hook(鉤子)的方式改變算法的走向,比如我們把hook()定義為一個boolean isHigh(),重新改變一下模板方法:

final void templatePipeLine() {
    installMainBoard();
    installCPU();
    if (isHigh)  {
      install3GRAM();
      install64GROM();
    } else{
      install2GRAM();
      install32GROM();
    }
}

這樣改造一下馏予,子類僅僅需要提供一下是否是高版本的這個條件就可以輕易的得到想要的流水線產(chǎn)品天梧,豈不是很方便?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末霞丧,一起剝皮案震驚了整個濱河市呢岗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛹尝,老刑警劉巖后豫,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異突那,居然都是意外死亡挫酿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門愕难,熙熙樓的掌柜王于貴愁眉苦臉地迎上來早龟,“玉大人,你說我怎么就攤上這事务漩≈羲ィ” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵饵骨,是天一觀的道長翘悉。 經(jīng)常有香客問我,道長居触,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮蟀拷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抬旺。我一直安慰自己,他們只是感情好祥楣,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布开财。 她就那樣靜靜地躺著,像睡著了一般误褪。 火紅的嫁衣襯著肌膚如雪责鳍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天兽间,我揣著相機(jī)與錄音历葛,去河邊找鬼。 笑死嘀略,一個胖子當(dāng)著我的面吹牛恤溶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帜羊,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼咒程,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逮壁?” 一聲冷哼從身側(cè)響起孵坚,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤粮宛,失蹤者是張志新(化名)和其女友劉穎窥淆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巍杈,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡忧饭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了筷畦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片词裤。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鳖宾,靈堂內(nèi)的尸體忽然破棺而出吼砂,到底是詐尸還是另有隱情,我是刑警寧澤鼎文,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布渔肩,位于F島的核電站,受9級特大地震影響拇惋,放射性物質(zhì)發(fā)生泄漏周偎。R本人自食惡果不足惜抹剩,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蓉坎。 院中可真熱鬧澳眷,春花似錦、人聲如沸蛉艾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勿侯。三九已至箍土,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罐监,已是汗流浹背吴藻。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留弓柱,地道東北人沟堡。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像矢空,于是被迫代替她去往敵國和親航罗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

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