初學設計模式,建議可以參考菜鳥教程,裏面講的很詳細,後來發現那裏也是從國外的翻譯過來的hhh,這裏我總結一下常見的23種設計模式。
一、設計模式的六大原則:
1、開閉原則(Open Close Principle)
開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,而是要擴展原有代碼,實現一個熱插拔的效果。所以一句話概括就是:爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類等。
2、里氏替換原則(Liskov Substitution Principle)
面向對象設計的基本原則之一,是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。在進行設計的時候,我們應該儘量從抽象類繼承,而不是從具體類繼承.如果從繼承等樹來看,所有葉子節點應當是具體類,而所有的樹枝節點應當是抽象類或者接口.當然這只是一個一般性的指導原則,使用的時候還要具體情況具體分析。
3、依賴倒轉原則(Dependence Inversion Principle)
這個是面向對象設計的核心原則,具體內容:針對接口編程,不針對實現編程。依賴於抽象而不依賴於具體。寫代碼用到具體類時,不與具體類交互,而與具體類的上層接口交互。
4、接口隔離原則(Interface Segregation Principle)
每個接口中不存在子類用不到卻必須實現的方法,如果不然,就要將接口拆分。使用多個隔離的接口,比使用單個接口(多個接口方法集合到一個的接口)要好。
5、迪米特法則(最少知道原則)(Demeter Principle)
一個類對自己依賴的類知道的越少越好。也就是說無論被依賴的類多麼複雜,都應該將邏輯封裝在方法的內部,通過public方法提供給外部。這樣當被依賴的類變化時,才能最小的影響該類。即一個軟件實體應當儘可能少地與其他實體發生相互作用,通過中間類建立聯繫。
6、合成複用原則(Composite Reuse Principle)
原則一句話:是儘量首先使用合成/聚合的方式,而不是使用繼承。
二、創建型模式(5種)
簡單工廠模式:
簡單工廠模式是屬於創建型模式,又叫做靜態工廠方法(Static Factory Method)模式,它不屬於23種GOF設計模式,但是可以被認爲是一種特殊的工廠方法模式,在簡單工廠模式中,可以根據參數的不同返回不同類的實例。簡單工廠模式專門定義一個類來負責創建其他類的實例,被創建的實例通常都具有相同的父類。
應用:
工廠方法模式:
簡單工廠模式有一個問題就是,類的創建依賴工廠類,也就是說,如果想要拓展程序,必須對工廠類進行修改,這違背了閉包原則,所以,從設計角度考慮,有一定的問題,如何解決?就用到工廠方法模式,在簡單工廠中,創建對象的是另一個類,而在工廠方法中,工廠父類負責創建對象的公共接口,工廠子類來創建具體的對象。
適用場景:
- 客戶不需要知道要使用的對象的創建過程
- 客戶使用的對象存在變動的可能,或者根本就不知道使用哪一個具體對象
優缺點:
- 一旦需要增加新的功能,直接增加具體工廠和具體產品就可以了,不需要修改之前的代碼。
- 增加新產品的同時需要增加新的工廠,導致系統類的個數成對增加,一定程度上增加了系統複雜性。
應用:
抽象工廠模式:
-
多個抽象產品類,每個抽象產品類可以派生出多個具體產品類。
-
一個抽象工廠類,可以派生出多個具體工廠類。
-
每個具體工廠類可以創建多個具體產品類的實例。
-
工廠方法模式只有一個抽象產品類,而抽象工廠模式有多個。
-
工廠方法模式的具體工廠類只能創建一個具體產品類的實例,而抽象工廠模式可以創建多個。
適用場景:
- 系統中有多個產品族,而系統一次只能消費其中一族產品
- 同屬於同一個產品族的產品一起使用
優缺點:
- 新增產品族很方便,只需要增加一個具體工廠即可。
- 但如果要增加新產品等級結構,就需要修改抽象工廠和所有具體工廠類,這種性質被稱爲 開閉原則 的傾斜性。
單例模式:
在Java應用中,單例對象能保證在一個JVM中,該對象只有一個實例存在。
單例模式的實現有懶漢式(線程不安全)、餓漢式(線程安全,一開始就直接實例化),靜態內部類實現等。
靜態內部類實現:
使用內部類來維護單例的實現,JVM內部的機制能夠保證當一個類被加載的時候,這個類的加載過程是線程互斥的。這樣當我們第一次調用getInstance的時候,JVM能夠幫我們保證instance只被創建一次,並且會保證把賦值給instance的內存初始化完畢,避免出現 JVM 具有指令重排的特性。
public class Singleton {
/* 私有構造方法,防止被實例化 */
private Singleton() {
}
/* 此處使用一個內部類來維護單例 */
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
/* 獲取實例 */
public static Singleton getInstance() {
return SingletonFactory.instance;
}
/* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */
public Object readResolve() {
return getInstance();
}
建造者模式:
建造者模式(Builder Pattern)使用多個簡單的對象一步一步構建成一個複雜的對象。
主要解決在軟件系統中,有時候面臨着"一個複雜對象"的創建工作,其通常由各個部分的子對象用一定的算法構成;由於需求的變化,這個複雜對象的各個部分經常面臨着劇烈的變化,但是將它們組合在一起的算法卻相對穩定。
如Java中的StringBuilder類的實現就是應用這種模式。
原型模式:
原型模式(Prototype Pattern)是用於創建重複的對象,同時又能保證性能。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式,用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
關鍵代碼:實現克隆操作,在 JAVA 實現 Cloneable,重寫 clone();
優點:
- 性能提高。
- 逃避構造函數的約束。
缺點:
- 配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類不是很難,但對於已有的類不一定很容易,特別當一個類引用不支持串行化的間接對象,或者引用含有循環結構的時候。
- 必須實現 Cloneable 接口。
三、結構型模式(7種)
適配器模式:
適配器模式(Adapter Pattern)是作爲兩個不兼容的接口之間的橋樑,它結合了兩個獨立接口的功能。如讀卡器是作爲內存卡和筆記本之間的適配器。您將內存卡插入讀卡器,再將讀卡器插入筆記本,這樣就可以通過筆記本來讀取內存卡。
方法:適配器繼承或依賴已有的對象,實現想要的目標接口。
優點:
- 可以讓任何兩個沒有關聯的類一起運行。
- 提高了類的複用。
- 增加了類的透明度。
缺點:
- 過多地使用適配器,會讓系統非常零亂,不易整體進行把握。
- 因 JAVA 至多繼承一個類,所以至多隻能適配一個適配者類,而且目標類必須是抽象類。
橋接模式:
將抽象和實現放在兩個不同的類層次中,使它們可以獨立地變化。——《Head First 設計模式》
將類的功能層次結構和實現層次結構相分離,使二者能夠獨立地變化,並在兩者之間搭建橋樑,實現橋接。—— 《圖解設計模式》
我們常用的JDBC橋DriverManager就是如此,JDBC進行連接數據庫的時候,在各個數據庫之間進行切換,基本不需要動太多的代碼,甚至絲毫不用動,原因就是JDBC提供統一接口,每個數據庫提供各自的實現,用一個叫做數據庫驅動的程序來橋接就行了。
組合模式:
組合模式(Composite Pattern),是用於把一組相似的對象當作一個單一的對象。將對象組合成樹形結構來表示“整體/部分”層次關係,允許用戶以相同的方式處理單獨對象和組合對象。
優點:
- 高層模塊調用簡單。
- 節點自由增加。
缺點:
- 在使用組合模式時,其葉子和樹枝的聲明都是實現類,而不是接口,違反了依賴倒置原則。
應用:(組合模式的使用場景就是出現樹形結構的地方)
- 文件目錄顯示,多及目錄呈現等樹形結構數據的操作
- java.util.Map#putAll(Map)
- java.util.List#addAll(Collection)
- java.util.Set#addAll(Collection)
裝飾者模式:
裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種它是作爲現有的類的一個包裝。這種模式創建了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。要求裝飾對象和被裝飾對象實現同一個接口。
優點:裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實現類的功能。
缺點:多層裝飾比較複雜。
使用場景:
- 擴展一個類的功能。
- 動態的爲一個對象增加功能,而且還能動態撤銷。(繼承不能做到這一點,繼承的功能是靜態的,不能動態增刪。)
外觀模式:
外觀模式(Facade Pattern)隱藏系統的複雜性,並向客戶端提供了一個客戶端可以訪問系統的接口,用來訪問子系統中的一羣接口,從而讓子系統更容易使用。
優點:
- 減少系統相互依賴。
- 提高靈活性。
- 提高了安全性。
缺點:
- 不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。
使用場景:
- JAVA 的三層開發模式。
- 假設一臺電腦,它包含了 CPU,Memory ,Disk)這幾個部件,若想要啓動電腦,則先後必須啓動 CPU、Memory、Disk。關閉也是如此。
但是實際上我們在電腦開/關機時根本不需要去操作這些組件,因爲電腦已經幫我們都處理好了,並隱藏了這些東西。
這些組件好比子系統角色(一個系統的子系統或模塊),而電腦就是一個外觀角色(客戶端通過操作外觀角色從而達到控制子系統角色的目的)。
享元模式:
享元模式的主要目的是實現對象的共享,即共享池,當系統中對象多的時候可以減少內存的開銷,通常與工廠模式一起使用。享元模式嘗試重用現有的同類對象,如果未找到匹配的對象,則創建新對象。
FlyWeightFactory負責創建和管理享元單元,當一個客戶端請求時,工廠需要檢查當前對象池中是否有符合條件的對象,如果有,就返回已經存在的對象,如果沒有,則創建一個新對象。
優點:大大減少對象的創建,降低系統的內存,使效率提高。
缺點:提高了系統的複雜度,需要分離出外部狀態和內部狀態,而且外部狀態具有固有化的性質,不應該隨着內部狀態的變化而變化,否則會造成系統的混亂。
應用:
- 1、JAVA 中的 String,如果有則返回,如果沒有則創建一個字符串保存在字符串緩存池裏面。
- 2、數據庫連接池,不需要每一次都重新創建連接,節省了數據庫重新創建的開銷。
- java.lang.Integer.valueOf()
代理模式:
在代理模式(Proxy Pattern)中,一個類代表另一個類的功能,我們創建具有現有對象的對象,以便向外界提供功能接口,爲其他對象提供一種代理以控制對這個對象的訪問。比如我們在租房子的時候回去找中介,因爲你對該地區房屋的信息掌握的不夠全面,希望找一個更熟悉的人去幫你做,此處的代理就是這個意思。再如我們有的時候打官司,我們需要請律師,因爲律師在法律方面有專長,可以替我們進行操作,表達我們的想法。
優點:
- 職責清晰。
- 高擴展性。
- 智能化。
缺點:
- 1、客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。
- 2、實現代理模式需要額外的工作,有些代理模式的實現非常複雜。
應用:
- java.lang.reflect.Proxy
- Spring Aop
注意:
- 區別適配器模式:適配器模式主要改變所考慮對象的接口,而代理模式不能改變所代理類的接口。
- 區別裝飾器模式:裝飾器模式爲了增強功能,而代理模式是爲了加以控制。
四、行爲型模式(11種)
責任鏈模式:
責任鏈模式(Chain of Responsibility Pattern),爲請求創建了一個接收者對象的鏈。有多個對象,每個對象持有對下一個對象的引用,這樣就會形成一條鏈,請求在這條鏈上傳遞,直到某一對象決定處理該請求。但是發出者並不清楚到底最終那個對象會處理該請求。
優點:
- 降低耦合度。它將請求的發送者和接收者解耦。
- 簡化了對象。使得對象不需要知道鏈的結構。
- 增強給對象指派職責的靈活性。通過改變鏈內的成員或者調動它們的次序,允許動態地新增或者刪除責任。
- 增加新的請求處理類很方便。
缺點:
- 不能保證請求一定被接收。
- 系統性能將受到一定影響,而且在進行代碼調試時不太方便,可能會造成循環調用。
- 可能不容易觀察運行時的特徵,有礙於除錯。
應用:
- java.util.logging.Logger.log()
- Apache Commons Chain
- javax.servlet.Filter.doFilter()
- Java Web 中 Apache Tomcat 對 Encoding 的處理
命令模式:
命令模式(Command Pattern)把一個請求或者操作封裝到一個對象中,把發出命令的責任和執行命令的責任分割開,委派給不同的對象,從使用角度來看就是請求者把接口實現類作爲參數傳給使用者,使用者直接調用這個接口的方法,而不用關心具體執行的那個命令。
優點:
- 降低了系統耦合度。
- 新的命令可以很容易添加到系統中去。
缺點:
- 使用命令模式可能會導致某些系統有過多的具體命令類。
應用:java.lang.Runnable
解釋器模式:
解釋器模式(Interpreter Pattern)實現了一個表達式接口,該接口解釋一個特定的上下文。給定一個語言,定義它的文法表示,並定義一個解釋器,這個解釋器使用該標識來解釋語言中的句子。
優點:
- 可擴展性比較好,靈活。
- 增加了新的解釋表達式的方式。
- 易於實現簡單文法。
缺點:
- 可利用場景比較少。
- 對於複雜的文法比較難維護。
- 解釋器模式會引起類膨脹。
- 解釋器模式採用遞歸調用方法。
使用場景:
- 可以將一個需要解釋執行的語言中的句子表示爲一個抽象語法樹。
- 一些重複出現的問題可以用一種簡單的語言來進行表達。
- 一個簡單語法需要解釋的場景。
迭代器模式:
迭代器模式(Iterator Pattern)提供一種方法順序訪問一個聚合對象中各個元素, 而又無須暴露該對象的內部表示。
優點:
- 它支持以不同的方式遍歷一個聚合對象。
- 迭代器簡化了聚合類。
- 在同一個聚合上可以有多個遍歷。
- 在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。
缺點:
- 由於迭代器模式將存儲數據和遍歷數據的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數成對增加,這在一定程度上增加了系統的複雜性。
應用:Java.util.Iterator
中介者模式:
中介者模式(Mediator Pattern)是用來降低多個對象和類之間的通信複雜性。用一箇中介對象來封裝一系列的對象交互。中介者使各對象不需要顯示地相互作用,從而使其耦合鬆散,而且可以獨立地改變它們之間的交互。
優點:
- 降低了類的複雜度,將一對多轉化成了一對一。
- 各個類之間的解耦。
- 符合迪米特原則。
缺點:
- 中介者會龐大,變得複雜難以維護。
應用:
- 系統中對象之間存在比較複雜的引用關係,導致它們之間的依賴關係結構混亂而且難以複用該對象。
- 想通過一箇中間類來封裝多個類中的行爲,而又不想生成太多的子類。
- MVC 框架,其中C(控制器)就是 M(模型)和 V(視圖)的中介者。
- java.lang.reflect.Method.invoke()。
備忘錄模式:
備忘錄模式(Memento Pattern),保存一個對象的某個狀態,以便在適當的時候恢復對象。在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。
優點:
- 1、給用戶提供了一種可以恢復狀態的機制,可以使用戶能夠比較方便地回到某個歷史的狀態。
- 2、實現了信息的封裝,使得用戶不需要關心狀態的保存細節。
缺點:
- 消耗資源。如果類的成員變量過多,勢必會佔用比較大的資源,而且每一次保存都會消耗一定的內存。
應用:
- 需要保存/恢復數據的相關狀態場景。
- 數據庫事務回滾。
觀察者模式:
當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern),又叫發佈-訂閱模式, 定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。類似於郵件訂閱,當你訂閱了該文章,如果後續有更新,會及時通知你。
優點:
- 觀察者和被觀察者是抽象耦合的。
- 建立一套觸發機制。
缺點:
- 如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
- 如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
- 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
狀態模式:
在狀態模式(State Pattern)中,類的行爲是基於它的狀態改變的,當對象的狀態改變時,同時改變其行爲,就拿QQ來說,有幾種狀態,在線、隱身、忙碌等,每個狀態對應不同的操作,而且你的好友也能看到你的狀態。
優點:
- 封裝了轉換規則。
- 枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類。
- 將所有與某個狀態有關的行爲放到一個類中,並且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行爲。
- 允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。
- 可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。
缺點:
- 狀態模式的使用必然會增加系統類和對象的個數。
- 狀態模式的結構與實現都較爲複雜,如果使用不當將導致程序結構和代碼的混亂。
- 狀態模式對"開閉原則"的支持並不太好。
使用場景:
- 行爲隨狀態改變而改變的場景。
- 條件、分支語句的代替者。
策略模式:
在策略模式(Strategy Pattern)中,一個類的行爲或其算法可以在運行時更改。需要定義一系列的算法,把它們一個個封裝起來, 並且使它們可相互替換。
優點:
- 算法可以自由切換。
- 避免使用多重條件判斷。
- 擴展性良好。
缺點:
- 策略類會增多。
- 所有策略類都需要對外暴露。
區分狀態模式:
狀態模式的類圖和策略模式類似,並且都是能夠動態改變對象的行爲。但是狀態模式是通過狀態轉移來改變 Context 所組合的 State 對象,而策略模式是通過 Context 本身的決策來改變組合的 Strategy 對象。所謂的狀態轉移,是指 Context 在運行過程中由於一些條件發生改變而使得 State 對象發生改變,注意必須要是在運行過程中。
狀態模式主要是用來解決狀態轉移的問題,當狀態發生轉移了,那麼 Context 對象就會改變它的行爲;而策略模式主要是用來封裝一組可以互相替代的算法族,並且可以根據需要動態地去替換 Context 使用的算法。
應用:
java.util.Comparator.compare()
javax.servlet.http.HttpServlet
javax.servlet.Filter.doFilter()
模板方法模式:
在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但調用將以抽象類中定義的方式進行,模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。
優點:
- 封裝不變部分,擴展可變部分。
- 提取公共代碼,便於維護。
- 行爲由父類控制,子類實現。
缺點:
- 每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。
應用:
- 有多個子類共有的方法,且邏輯相同。
- java.util.Collections.sort()。
訪問者模式:
在訪問者模式(Visitor Pattern)中,我們使用了一個訪問者類,它改變了元素類的執行算法。通過這種方式,元素的執行算法可以隨着訪問者改變而改變。根據模式,元素對象已接受訪問者對象,這樣訪問者對象就可以處理元素對象上的操作。
優點:
- 符合單一職責原則。
- 優秀的擴展性。
- 靈活性。
缺點:
- 具體元素對訪問者公佈細節,違反了迪米特原則。
- 具體元素變更比較困難。
- 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。
應用:
需要對一個對象結構中的對象進行很多不同的並且不相關的操作,而需要避免讓這些操作"污染"這些對象的類,使用訪問者模式將這些封裝到類中。
補充:
- 想更清楚瞭解UML圖的話可以參考設計模式中的類圖實例
- JDK中的設計模式
- Spring中涉及到的設計模式
- Mybatis使用的設計模式
參考資料:
- 《設計模式》清華大學出版社劉偉主編
- 菜鳥教程