設計模式大雜燴

設計模式大雜燴(24種設計模式的總結以及學習設計模式的幾點建議)     

         迄今爲止,LZ已經將24種設計模式介紹完了,其中包括GOF23種設計模式以及簡單工廠模式,這些設計模式之間並不是完全獨立的,而是互相之間,會有一些相同的影子,下面我們來一起總結下這24種設計模式。

 

模式分類 & 傳送門 & 對比維度說明

 

 

        以上便是設計模式的分類以及各個模式的傳送門,可以看到其中行爲型模式的個數爲最多,結構型次之,創建型設計模式最少。

        在寫這篇文章的時候,LZ考慮的最多的一個問題就是,從哪幾個維度去對比設計模式能讓大家更加清楚的看出各個設計模式的區別與聯繫,思來想去,LZ決定從以下幾個維度去對比設計模式。

 

  • 設計原則:描述每個設計模式都遵循了哪些設計原則,破壞了哪些設計原則。
  • 常用場景:描述各個設計模式大部分情況下,都會在哪些場景下出現。
  • 使用概率:主要指在普遍的工作當中,該設計模式出現的頻率,若是類庫或是開源框架提供的功能中包含該模式,則也會計算其頻率。
  • 複雜度:特指一個設計模式在實現的時候的複雜度,主要的衡量標準是類的數量、類之間的耦合關係。
  • 變化點:設計模式很大的一個意義在於容納變化,掌握一個設計模式的變化點是非常重要的一件事。
  • 選擇關鍵點:當選擇使用一個設計模式的時候,指出最關鍵的選擇點在哪裏。
  • 逆鱗:龍有逆鱗,不可觸摸,同樣的,設計模式也有逆鱗,有些地方是不能碰的。
  • 相關設計模式:與其它設計模式的關係。

 

創建型設計模式

單例模式

  • 設計原則:無
  • 常用場景:應用中有對象需要是全局的且唯一
  • 使用概率:99.99999%
  • 複雜度:低
  • 變化點:無
  • 選擇關鍵點:一個對象在應用中出現多個實例是否會引起邏輯上或者是程序上的錯誤
  • 逆鱗:在以爲是單例的情況下,卻產生了多個實例
  • 相關設計模式
    • 原型模式:單例模式是隻有一個實例,原型模式每拷貝一次都會創造一個新的實例。

簡單工廠模式

  • 設計原則:遵循單一職責、違背開閉原則
  • 常用場景:需要在一堆產品中選擇其中一個產品
  • 使用概率:99.99999%
  • 複雜度:低
  • 變化點:產品的種類
  • 選擇關鍵點:一種產品是否可根據某個參數決定它的種類
  • 逆鱗:工廠類不能正常工作
  • 相關設計模式
    • 工廠方法模式:工廠方法模式是簡單工廠模式的進一步抽象化,在這兩者之間做選擇,主要看將工廠進一步抽象化是否有必要,通常情況下,如果工廠的作用僅僅是用來製造產品,則沒必要使用工廠方法模式。

工廠方法模式

  • 設計原則:遵循單一職責、依賴倒置、開閉原則
  • 常用場景:一種場景是希望工廠與產品的種類對客戶端保持透明,給客戶端提供一致的操作,另外一種是不同的工廠和產品可以提供客戶端不同的服務或功能
  • 使用概率:60%
  • 複雜度:中低
  • 變化點:工廠與產品的種類
  • 選擇關鍵點:工廠類和產品類是否是同生同滅的關係
  • 逆鱗:無
  • 相關設計模式
    • 抽象工廠模式:工廠方法模式與抽象工廠模式最大的區別在於,在工廠方法模式中,工廠創造的是一個產品,而在抽象工廠模式中,工廠創造的是一個產品族。

 

抽象工廠模式

  • 設計原則:遵循單一職責、依賴倒置、開閉原則
  • 常用場景:需要一個接口可以提供一個產品族,且不必知道產品的具體種類
  • 使用概率:30%
  • 複雜度:中
  • 變化點:工廠與產品的種類
  • 選擇關鍵點:產品族是否需要一起提供,且是否有一致的接口
  • 逆鱗:無
  • 相關設計模式
    • 建造者模式:兩者都是建造一批對象或者說產品,不同的是兩者的目的和實現手段,在建造者模式中,是爲了複用對象的構建過程而定義了一個指揮者,而在抽象工廠模式中,是爲了提供一個這批對象的創建接口而定義了抽象工廠接口。

建造者模式

  • 設計原則:遵循單一職責、開閉原則
  • 常用場景:需要構建一批構建過程相同但表示不同的產品,而構建過程非常複雜
  • 使用概率:10%
  • 複雜度:中
  • 變化點:產品的表示
  • 選擇關鍵點:各個產品的構建過程是否相同
  • 逆鱗:指揮者不能正常工作

原型模式

  • 設計原則:無
  • 常用場景:需要在運行時動態的創建指定實例種類的對象,或是需要複用其狀態
  • 使用概率:10%
  • 複雜度:中低
  • 變化點:無
  • 選擇關鍵點:創建出來的對象是否可以立即投入使用
  • 逆鱗:在以爲是深度拷貝的情況下,卻未實現深度拷貝

 

結構型設計模式

代理模式

  • 設計原則:體現功能複用
  • 常用場景:需要修改或屏蔽某一個或若干個類的部分功能,複用另外一部分功能,可使用靜態代理,若是需要攔截一批類中的某些方法,在方法的前後插入一些一致的操作,假設這些類有一致的接口,可使用JDK的動態代理,否則可使用cglib
  • 使用概率:99.99999%
  • 複雜度:中高
  • 變化點:靜態代理沒有變化點,動態代理的變化點爲具有相同切入點的類
  • 選擇關鍵點:靜態代理選擇的關鍵點是是否要複用被代理的部分功能,動態代理選擇的關鍵點在於能否在將被代理的這一批類當中,找出相同的切入點
  • 逆鱗:切入點的不穩定
  • 相關設計模式
    • 適配器模式:對於適配器模式當中的定製適配器,它與靜態代理有着相似的部分,二者都有複用功能的作用,不同的是,靜態代理會修改一部分原有的功能,而適配器往往是全部複用,而且在複用的同時,適配器還會將複用的類適配一個接口

適配器模式

  • 設計原則:遵循開閉原則、體現功能複用
  • 常用場景:需要使用一個類的功能,但是該類的接口不符合使用場合要求的接口,可使用定製適配器,又或者是有一個接口定義的行爲過多,則可以定義一個缺省適配器,讓子類選擇性的覆蓋適配器的方法
  • 使用概率:40%
  • 複雜度:中
  • 變化點:無
  • 選擇關鍵點:定製適配器的選擇關鍵點在於是否有更加優良的替代方案,缺省適配器的選擇關鍵點在於接口中的方法是否可以不全部提供,且都有缺省方案
  • 逆鱗:無
  • 相關設計模式
    • 裝飾器模式:對於適配器模式中的定製適配器與裝飾器模式,二者都是使用組合加繼承的手段,不同的是,適配器模式的目的在於適配接口,裝飾器模式的目的在於動態的添加功能,且可以疊加。

裝飾器模式

  • 設計原則:遵循迪米特、單一職責、開閉原則,破壞里氏替換,體現功能複用
  • 常用場景:一個類需要動態的添加功能,且這些功能可以相互疊加
  • 使用概率:99.99999%
  • 複雜度:中
  • 變化點:動態添加的功能或者說裝飾器
  • 選擇關鍵點:添加的功能是否需要動態組裝
  • 逆鱗:無

橋接模式

  • 設計原則:遵循單一職責、迪米特、開閉原則,體現功能複用
  • 常用場景:一個對象有多個維度的變化,需要將這些維度抽離出來,讓其獨立變化
  • 使用概率:20%
  • 複雜度:中高
  • 變化點:維度的擴展與增加
  • 選擇關鍵點:是否可以將對象拆分成多個不相關的維度
  • 逆鱗:無

組合模式

  • 設計原則:遵循依賴倒置、開閉原則,破壞接口隔離
  • 常用場景:當有一個結構可以組合成樹形結構,且需要向客戶端提供一致的操作接口,使得客戶端操作忽略簡單元素與複雜元素
  • 使用概率:30%
  • 複雜度:中
  • 變化點:節點的數量
  • 選擇關鍵點:對外提供一致操作接口的結構是否可轉化爲樹形結構
  • 逆鱗:結構不穩定或結構中的節點有遞歸關係

享元模式

  • 設計原則:無
  • 常用場景:一些狀態相同的對象被大量的重複使用
  • 使用概率:90%
  • 複雜度:中
  • 變化點:無
  • 選擇關鍵點:被共享的對象是否可以將外部狀態提取出來
  • 逆鱗:沒有將外部狀態提取完全

外觀模式

  • 設計原則:遵循迪米特
  • 常用場景:一個子系統需要對外提供服務
  • 使用概率:60%
  • 複雜度:中
  • 變化點:無
  • 選擇關鍵點:子系統對外提供服務是否需要依賴很多的類
  • 逆鱗:子系統對外提供的服務的變化或子系統本身的不穩定
  • 相關設計模式
    • 中介者模式:二者都是爲了處理複雜的耦合關係,不同的是外觀模式處理的是類之間複雜的依賴關係,中介者模式處理的是對象之間複雜的交互關係

 

行爲型設計模式

觀察者模式

  • 設計原則:遵循迪米特、開閉原則
  • 常用場景:需要將觀察者與被觀察者解耦或者是觀察者的種類不確定
  • 使用概率:40%
  • 複雜度:中
  • 變化點:觀察者的種類與個數
  • 選擇關鍵點:觀察者與被觀察者是否是多對一的關係
  • 逆鱗:觀察者之間有過多的細節依賴

模板方法模式

  • 設計原則:破壞里氏替換,體現功能複用
  • 常用場景:一批子類的功能有可提取的公共算法骨架
  • 使用概率:80%
  • 複雜度:中低
  • 變化點:算法骨架內各個步驟的具體實現
  • 選擇關鍵點:算法骨架是否牢固
  • 逆鱗:無

命令模式

  • 設計原則:遵循迪米特、開閉原則
  • 常用場景:行爲的請求者與行爲的處理者耦合度過高
  • 使用概率:20%
  • 複雜度:中高
  • 變化點:命令的種類
  • 選擇關鍵點:請求者是否不需要關心命令的執行只知道接受者
  • 逆鱗:命令的種類無限制增長
  • 相關設計模式
    • 職責鏈模式:容易將二者關聯在一起的原因是,二者都是爲了處理請求或者命令而存在的,而且二者都是爲了將請求者與響應者解耦,不同的是命令模式中,客戶端需要知道一個命令的接受者,在創建命令的時候就把接受者與命令綁定在一起發送給調用者,而職責鏈模式中,客戶端並不關心最終處理請求的對象是誰,客戶端只是封裝一個請求對象,隨後交給職責鏈的頭部而已,也正因爲這樣,二者的實現方式,有着很大的區別

狀態模式

  • 設計原則:遵循單一職責、依賴倒置、開閉原則
  • 常用場景:一個對象在多個狀態下行爲不同,且這些狀態可互相轉換
  • 使用概率:20%
  • 複雜度:中
  • 變化點:狀態的種類
  • 選擇關鍵點:這些狀態是否經常在運行時需要在不同的動態之間相互轉換
  • 逆鱗:無
  • 相關設計模式
    • 策略模式:二者的實現方式非常相似,策略接口與狀態接口,具體的策略與具體的狀態以及二者都擁有的上下文,如果看它們的類圖,會發現幾乎一模一樣,而二者不同的地方就在於,狀態模式經常會在處理請求的過程中更改上下文的狀態,而策略模式只是按照不同的算法處理算法邏輯,而且從實際場景來講,顧名思義,狀態模式改變的是狀態,策略模式改變的是策略

職責鏈模式

  • 設計原則:遵循迪米特
  • 常用場景:一個請求的處理需要多個對象當中的一個或幾個協作處理
  • 使用概率:15%
  • 複雜度:中
  • 變化點:處理鏈的長度與次序
  • 選擇關鍵點:對於每一次請求是否每個處理的對象都需要一次處理機會
  • 逆鱗:無

解釋器模式

  • 設計原則:遵循單一職責
  • 常用場景:有一種語言被頻繁的使用
  • 使用概率:0.00009%
  • 複雜度:中高
  • 變化點:語言的規則
  • 選擇關鍵點:被頻繁使用的語言是否可用文法表示
  • 逆鱗:語言的規則無限制增長或規則十分不穩定

中介者模式

  • 設計原則:遵循迪米特,破壞單一職責
  • 常用場景:一個系列的對象交互關係十分複雜
  • 使用概率:10%
  • 複雜度:中
  • 變化點:對象之間的交互
  • 選擇關鍵點:複雜的交互關係是否有共性可被中介者承擔
  • 逆鱗:中介者無法工作

訪問者模式

  • 設計原則:遵循傾斜的開閉原則
  • 常用場景:作用於一個數據結構之上的操作經常變化
  • 使用概率:5%
  • 複雜度:高
  • 變化點:數據結構之上的操作
  • 選擇關鍵點:數據結構是否穩定以及操作是否經常變化
  • 逆鱗:數據結構的不穩定

策略模式

  • 設計原則:遵循單一職責、依賴倒置、迪米特、開閉原則
  • 常用場景:算法或者策略需要經常替換
  • 使用概率:60%
  • 複雜度:中
  • 變化點:策略的種類
  • 選擇關鍵點:客戶端是否依賴於某一個或若干個具體的策略
  • 逆鱗:無

備忘錄模式

  • 設計原則:遵循迪米特、開閉原則
  • 常用場景:需要在對象的外部保存該對象的內部狀態
  • 使用概率:5%
  • 複雜度:中
  • 變化點:無
  • 選擇關鍵點:是否可以在必要的時候捕捉到對象的內部狀態
  • 逆鱗:大對象的備份

迭代器模式

  • 設計原則:遵循迪米特
  • 常用場景:需要迭代訪問一個聚合對象中的各個元素,且不暴露該聚合對象內部的表示
  • 使用概率:99.99999%
  • 複雜度:中
  • 變化點:聚合對象的種類
  • 選擇關鍵點:客戶端是否關心遍歷的次序
  • 逆鱗:無
  • 相關設計模式
    • 訪問者模式:二者都是迭代的訪問一個聚合對象中的各個元素,不同的是,訪問者模式中,擴展開放的部分在作用於對象的操作上,而迭代器模式中,擴展開放的部分在聚合對象的種類上,而且二者的實現方式也有着很大的區別。

結束語

        以上便是24種設計模式的各個特點與部分模式的對比,如果總結的過程當中有疏漏或是錯誤,請各位不吝賜教,LZ感激不盡。

        此外需要說明的是,上面的概率當中有的會出現99.99999%這樣的數字,這是因爲這個模式已經嵌入到JAVA類庫或是我們常用的開源框架當中(標註一下:LZ總結的主要針對WEB開發,android的開發LZ並未接觸過,所以不包括在此列),比如迭代器模式,只要你使用過ArrayList或者HashSet,LZ就認爲這個模式被使用。這個使用頻率從某種意義上講,可以認爲是該模式的重要程度,當然由於這個頻率是LZ制定的,所以僅代表個人觀點。

        這裏再針對設計模式的學習,給各位提一點點建議,僅供參考,請各位吸優排差,次序不分先後。

        1、之前說過,學習設計模式除了努力之外還要靠緣分,所以如果有設計模式當時怎麼看都不明白,可以暫且放下,之後說不定哪天你突然之間就明白了。(此話並非虛言,LZ很多次的頓悟常發生在上廁所、洗澡、回家路上等一些學習之外的時候。)

        2、對於已經在工作的人來說,可以常思考一下,有沒有哪個設計模式可以改善現有的系統架構,但不要輕易付諸實踐。

        3、學習設計模式之前,一定要先整明白UML類圖,什麼關聯,依賴,聚合,組合等等都得搞明白兒的,否則學習起來也依然會很吃力。

        4、對於初學者,一定要在弄清楚標準的實現代碼之後,寫一個屬於自己的例子,哪怕是比葫蘆畫瓢,然後仔細體會設計模式使用前後的差異,主要從擴展性和類(類包括客戶端,而不僅僅指設計模式中的角色)的職責兩個方面。

        5、一定要將設計模式的變化點搞清楚,這點非常重要,甚至重要程度高於設計模式的場景、實現方式以及類和對象之間的耦合關係,很多時候,設計模式的濫用就是因爲變化點沒搞清楚,以至於該變化的沒變化,不該變化的經常變化,增加系統的負擔。

        6、設計模式不是一次性學習完就可以扔掉不看的東西,而是要經常回過頭來看看,說不定每一次你都有不一樣的體會,而且一般情況下,這些體會會越來越深刻,越來越透徹。

        7、如果可能的話,多研究一些開源框架,去找找它們裏面的設計模式。

        LZ暫時也就只能想到這些,如果以後有想到再補充吧,各位如果有什麼好的建議也可以與LZ分享一下。

        到此爲止,整個設計模式系列就真真正正的徹底結束了,當初寫的時候也沒想到自己可以真的堅持下來,雖說整整26篇文章不算多,但是LZ確實花費了大量的時間和精力,值得欣慰的是LZ本人也得到了巨大的收穫,不僅僅是對設計模式的理解日益加深,而且還得了不少猿友的支持,讓LZ對分享這一道路更加堅定。

        以後的編程之路還很長,對於LZ來說,編程並不僅僅是工作,而是一份事業,它給了LZ榮譽、金錢、成就感等等很多東西,希望各位至少在年輕的時候不要被一些悲觀化的觀點所幹擾,特別是對編程有着熱愛的猿友們,極致才能成就大道,但凡在一個領域有所成就者,大都是鑽研了數十年的成果。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章