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

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


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

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

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

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

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

非策略模式.png

很明顯所有的手機(jī)機(jī)型都需要繼承一個(gè)基類,而且所有手機(jī)都具有打電話這個(gè)方法且都是一致的,所以把打電話放在基類里面是可以的。但是拍照這個(gè)方法不可以,因?yàn)槊總€(gè)機(jī)型拍照的能力不一樣,如果放在基類里面則都會(huì)被繼承,這樣是不對(duì)的。所以常見(jiàn)的方法是寫一個(gè)接口,每個(gè)機(jī)型都去繼承這個(gè)接口并各自實(shí)現(xiàn),這樣就可以解決問(wèn)題。但是這又帶來(lái)另外一個(gè)問(wèn)題,這個(gè)接口實(shí)在是被繼承太多次了!而且只是為了一個(gè)不同的行為,假設(shè)還有其他行為如打游戲又需要繼承另一個(gè)接口,簡(jiǎn)單算個(gè)賬,假設(shè)總共有2300萬(wàn)部手機(jī),5個(gè)不同接口,2300萬(wàn)*5,很大的一個(gè)數(shù)字.....那么沒(méi)有辦法把它放在父類里面去繼承嗎?


答案:是可以的,而且很簡(jiǎn)單,“就把它放在父類里面啊!”(感覺(jué)自己在找打)實(shí)現(xiàn)如下:

策略模式.png

我們按類圖的上下兩部分來(lái)說(shuō):

  • 封裝行為

    首先我們依然把camera當(dāng)成一個(gè)接口,這一點(diǎn)和非策略模式是一致的,但是我們有了具體實(shí)現(xiàn)類,光學(xué)防抖的camera,超感光元器件的camera,變焦雙攝的camera,并且我們?cè)诓煌腸amera里面實(shí)現(xiàn)了自己的takePhoto方法,這樣行為就被封裝了。

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

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

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

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

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

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

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

抽象工廠模式.png

這里我們把工廠更具體化了一點(diǎn)(可能實(shí)際上只需要一家工廠),我們有兩個(gè)工廠,一個(gè)是海淀工廠,一個(gè)是朝陽(yáng)工廠,每個(gè)工廠提供不同的元器件。這兩個(gè)工廠生產(chǎn)手機(jī)的能力有什么不同呢?答案可以從流水線上看出來(lái),很明顯海淀工廠可以擁有兩條流水線,一個(gè)是小米5s,一個(gè)是小米5sPlus,僅僅只需要把屏幕的大小換一下(實(shí)際上肯定還有其他的元器件,這里只用屏幕大小來(lái)區(qū)別)。然而朝陽(yáng)工廠只擁有一條流水線即米6。
這個(gè)時(shí)候我們有一家海淀的線下店缺貨了,這個(gè)時(shí)候需要“造一些”手機(jī),這里我們把用海淀工廠來(lái)造手機(jī)(當(dāng)然用朝陽(yáng)工廠也行,或者兩個(gè)工廠用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();
    }

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

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

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

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

很明顯可以看出小米6與小米5的不同之處,小米5是單攝像頭,小米6是雙攝像頭,同時(shí)小米5和小米6的屏幕也是不一樣的。這樣會(huì)有什么影響呢?我們從兩個(gè)方面談一下:

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

好了,廢話扯完了,該扯到設(shè)計(jì)模式了,假設(shè)不用設(shè)計(jì)模式,我們需要得到一臺(tái)機(jī)型的價(jià)格和描述我們?cè)撊绾巫觯靠赡苋缦拢?/p>

非裝飾者模式.png

這是一個(gè)看上去簡(jiǎn)單實(shí)際上很傻的設(shè)計(jì),我們把cost實(shí)現(xiàn)全部放在具體的機(jī)型里面,這樣的后果是可怕的——假設(shè)攝像頭是從美國(guó)采購(gòu)回來(lái)的,因?yàn)閰R率問(wèn)題,價(jià)格變化了,我們需要在每個(gè)機(jī)型的cost方法里面重新定義價(jià)格,就算我們改良一下,我們把所有的元器件都放在父類里面定義,這樣好嗎?也不好,這樣所有的機(jī)型都必須繼承這些器件,假設(shè)米5繼承了父類的四曲面screen,實(shí)際上根本用不著,而且顯然父類是需要維護(hù)的而且維護(hù)的成本不低,這并不是一個(gè)父類該做的事情,它應(yīng)該只保留子類共性的部分并提供抽象的方法。好吧,用裝飾者模式改造一下設(shè)計(jì)吧:

裝飾者模式.png

我們分成三部分來(lái)討論,被裝飾者裝飾者裝飾過(guò)程

  • 被裝飾者
    被裝飾者這里指的就是小米5和小米6,它們同時(shí)繼承了手機(jī)這個(gè)抽象類并且各種實(shí)現(xiàn)了cost方法,但是這樣還夠,因?yàn)樗鼈儍H僅是孤零零的裸機(jī),什么都沒(méi)有,沒(méi)有攝像頭,沒(méi)有屏幕也沒(méi)有cpu,我們需要裝飾一下它們。

  • 裝飾者
    我們需要定義一個(gè)抽象的decorator來(lái)繼承抽象父類phone,這樣做是為了繼承父類的抽象方法cost和getDescription。實(shí)際的裝飾者是誰(shuí)?是那些元器件,比如攝像頭四曲面玻璃驍龍cpu等等,我們用這些描述累加起來(lái)去具體形容一個(gè)機(jī)型,同時(shí)我們?cè)诶锩孢€順便實(shí)現(xiàn)一下cost和getDescription方法(如何實(shí)現(xiàn)看裝飾過(guò)程),要注意實(shí)際的裝飾者必須關(guān)聯(lián)一個(gè)實(shí)際的被裝飾者phone(定義在屬性里面)。

  • 裝飾過(guò)程
    我們先來(lái)裝飾一個(gè)小米5
    Phone xiaomi5phone = new 大猩猩screen(new 驍龍cpu(new camera (new fingerPrinter(new xiaomi5()))))
    首先,這么做可以嗎?毫無(wú)疑問(wèn)是可以的,因?yàn)槊總€(gè)裝飾者的構(gòu)造方法里面都必須傳入一個(gè)已經(jīng)初始化過(guò)的phone對(duì)象,而且每個(gè)裝飾者本身又可以是被裝飾的對(duì)象。這樣就有趣了,我們可以一直裝飾下去。我們可以用用攝像頭裝飾米5,可以描述成有攝像頭的米5,我們可以繼續(xù)用大猩猩屏幕來(lái)裝飾有攝像頭的米5,這樣就可以描述成有大猩猩屏幕且有攝像頭的米5......米6我們?nèi)匀豢梢赃@樣裝飾
    Phone xiaomi6phone = new 四曲面screen(new 驍龍cpu(new camera(new camera(new fingerPrinter(new xiaomi6())))))
    我們甚至可以用camera裝飾兩次讓米6變成雙攝的手機(jī)
    我們?cè)賹?shí)現(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();
        }
      }
    

很容易看出來(lái)無(wú)論是描述還是價(jià)格都具有累加的屬性,這正是我前面所強(qiáng)調(diào)的地方,這樣我們就可以方便的獲取到裝飾后的價(jià)格和描述,因?yàn)檫@些屬性均具有累加的性質(zhì),這就是裝飾者模式的三個(gè)部分。

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

前面說(shuō)過(guò),手機(jī)在工廠中的加工方法其實(shí)就是一條流水線,往往很多手機(jī)的流水線是相似的,僅僅只是修改了流水線中的一兩個(gè)步驟,這次我們拿小米5標(biāo)準(zhǔn)版和小米5高配版來(lái)做個(gè)對(duì)比,這兩條流水線如下(圖畫的有點(diǎn)歪見(jiàn)諒):

流程圖模擬流水線.png

很明顯,搞出來(lái)兩條流水線是浪費(fèi)的,因?yàn)樾∶?標(biāo)準(zhǔn)版和高配版之間的區(qū)別僅僅是內(nèi)存和rom的不同,所以我們可以合并一下流程:

合并流水線.png

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


我們先來(lái)看看模板方法模式的定義:

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

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

模板方法模式.png

我們?cè)谧宇愔兄匦露x流水線的兩個(gè)步驟installRAM()installROM,我們?cè)購(gòu)拇a上看一下父類的流水線這個(gè)方法:

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,606評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,044評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,227評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,447評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評(píng)論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,667評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,930評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評(píng)論 2 374

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