總結常見的 24 種設計模式的使用要點及其 Java 實現

來源:互聯網
上載者:User

設計模式是在不斷出現的特定情境下,針對特定問題,可以重複使用的特定解決模式(套路)。本文按照建立型、結構型、行為型三大類,總結了常見的 24 種設計模式的使用要點,包括適用情境、解決方案、及其相應的 Java 實現。

1 概述

1.1 概念

設計模式,是在某個不斷出現的“情境(Context)”下,針對某個“問題”的某種“解決方案”:

  • “問題”必須是重複出現的,“解決方案”必須是可反覆應用的;

  • “問題”包含了“一個目標”和“一組約束”,當解決方案在兩者之間取得平衡,才是有用的模式;

  • 設計模式不是法律準則,只是指導方針,實際使用時可以根據需要微調,只是要作好注釋,以便他人清楚;

  • 很多看似的新模式,實質上是現有模式的變體;

  • 模式的選用原則:盡量用最簡單的方式設計,除非為了適應未來確實可能的變化,才採用設計模式,因為設計模式會引入更多類更複雜的關係,不要為了使用模式而使用模式。

1.2 六大原則

將六大原則的英文首字母拼在一起就是SOLID(穩定的),所以也稱之為 SOLID 原則。

1.2.1 單一職責原則(Single Responsibility Principle)

There should never be more than one reason for a class to change.
一個類只有一個職責,而不是多個職責耦合在一個類中(比如介面與邏輯要分離)。

1.2.2 開放封閉原則(Open Closed Principle)

Software entities like classes, modules and functions should be open for extension but closed for modifications.
對擴充開放,對修改關閉,使用介面和抽象類別。

1.2.3 裡氏替換原則(Liskov Substitution Principle)

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
確保父類可以出現的地方,子類一定可以出現,這是繼承複用的基石。

1.2.4 最少知道原則(Least Knowledge Principle)

Only talk to you immediate friends.
低依賴,各實體盡量獨立,盡量減少相互作用。

1.2.5 介面隔離原則(Interface Segregation Principle)

The dependency of one class to another one should depend on the smallest possible interface.
客戶(client)應該不依賴於它不使用的方法。盡量使用多個介面分工合成,而不是單個介面耦合多種功能。

1.2.6 依賴倒置原則(Dependency Inversion Principle)

High level modules should not depends upon low level modules.
Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
要依賴於抽象(介面或抽象類別),而不是具體(具體類)。

1.3 價值

設計模式(Design Pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。
設計模式看似簡單問題複雜化。但“簡單”的設計靈活性差,在當前項目中不便擴充,拿到其他項目更是無法使用,相當於“一次性代碼”。而設計模式的代碼,結構清晰,當前項目中便於擴充,拿到其他項目也適用,是通用的設計。
很多程式員接觸到設計模式之後,都有相見恨晚的感覺,感覺自己脫胎換骨,達到了新的境界,設計模式可以作為程式員劃分水平的標準。
不過我們也不能陷入模式的陷阱,為了使用模式而去套模式,那樣會陷入形式主義。

1.4 選用方法

  • 每個設計模式,都隱含了幾個OO原則,當沒有合適的設計模式可選時,可迴歸到OO原則來取捨;

  • 使用模式最好的方式:腦子裡裝著各種模式,看已有設計或代碼中,哪裡可以使用這些模式,以複用經驗;

  • 共用設計模式詞彙(包括口頭叫法、代碼中類與方法的命名)的威力:
    (1)與他人溝通時,提到設計模式名稱,就隱含了其模式;
    (2)使用模式觀察軟體系統,可以保持在設計層次,而不會被停留在瑣碎的對象細節上;
    (3)團隊間用設計模式交流,彼此看法不容易誤解。

1.5 重要書籍

作者:埃裡希·伽瑪(Erich Gamma), Richard Helm , Ralph Johnson,John Vlissides,後以“四人幫”(Gang of Four,GoF)著稱。有兩本書:

1.5.1 《Head First 設計模式》

強烈推薦閱讀。英文書名是《Head First Design Patterns》。
信耶穌的人都要讀聖經,信OO(物件導向)的人都要讀四人組的《Head First 設計模式》,官方網站 Head First Design Patterns。2004 該書榮獲Jolt獎(類似於電影領域的奧斯卡獎)。

  • 是首次將模式歸類的功臣,開啟了軟體領域的一大躍進;

  • 模式的模板:包括名稱、類目、意圖、動機、適用性、類圖、參與者及其協作、結果、實現、範例代碼、已知應用、相關模式等。

1.5.2 《設計模式:可複用物件導向軟體的基礎》

英文書名是《Design Patterns: Elements of Reusable Object-Oriented Software》。也是四人組所著。
是軟體工程領域有關軟體設計的一本書,提出和總結了對於一些常見軟體設計問題的標準解決方案,稱為軟體設計模式。這本書在1994年10月21日首次出版,至2012年3月已經印行40刷。

2 分類與定義

設計模式可分為三個大類,每個大類又包含若干具體的模式。
容易混淆的幾種模式:簡單工廠S / 抽象工廠A / Factory 方法F / 模板方法T

  • “工廠”字樣的:帶的只用來建立執行個體,比如 S/A/F;不帶的則不限,比如 T;

  • “方法”字樣的:帶的無需額外的用戶端參與,可以獨立運轉,比如 F/T;不帶的需要額外的用戶端調用,比如 S/A。

2.1 建立型(Creational Patterns)

用於對象的建立,把建立對象的工作放在另一個對象中,或者延遲到子類中。

2.1.1 單例(Singleton)

確保一個類只有一個執行個體,並提供一個全域的訪問點。
需要注意的是,多個類載入器下使用單例,會導致各類載入器下都有一個單例執行個體,因為每個類載入器都有自己獨立的命名空間。
JDK 中的單例有 Runtime.getRuntime()NumberFormat.getInstance()
下面總結了四種安全執行緒的 Java 實現方法。每種實現都可以用 Singleton.getInstance().method(); 調用。

2.1.1.1 餓漢方式

關鍵思路:作為類的靜態全域變數,載入該類時執行個體化。
缺點是真正使用該執行個體之前(也有可能一直沒用到),就已經執行個體化,浪費資源。
對於 Hotspot VM,如果沒涉及到該類,實際上是首次調用 getInstance() 時才執行個體化。

/** * @author: kefeng.wang * @date: 2016-06-07 10:21 **/public class Singleton {    private static Singleton instance = new Singleton();    private Singleton() {    }    // 基於 classLoader 機制,自動達到了安全執行緒的效果    public static Singleton getInstance() {        return instance;    }    public void method() {        System.out.println("method() OK.");    }}
2.1.1.2 懶漢方式

關鍵思路:在方法 getInstance() 上實現同步。
缺點是每次調用 getInstance() 都會加鎖,但實際上只有首次執行個體化時才需要,後續的加鎖都是浪費,導致效能大降。

/** * @author: kefeng.wang * @date: 2016-06-07 10:21 **/public class Singleton {    private static Singleton instance = null;    private Singleton() {    }    public static synchronized Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }    public void method() {        System.out.println("method() OK.");    }}
2.1.1.3 懶漢方式(雙重檢查加鎖)

關鍵思路:不同步的情況下檢查到尚未建立,再同步檢查到尚未執行個體化時,才執行個體化。以便大大減少同步的情況。
缺點是:要求 JDK5+,否則許多 JVM 對 volatile 的實現導致雙重加鎖失效。不過現在極少開發人員會用 JDK5,所以該缺點關係不大。

/** * @author: kefeng.wang * @date: 2016-06-07 10:21 **/public class Singleton {    private volatile static Singleton instance = null; // 注意 volatile    private Singleton() {    }    public static Singleton getInstance() {        if (instance == null) { // 初步檢查:尚未執行個體化            synchronized (Singleton.class) { // 再次同步(對 Singleton.class)                if (instance == null) { // 確認尚未執行個體化                    instance = new Singleton();                }            }        }        return instance;    }    public void method() {        System.out.println("method() OK.");    }}
2.1.1.4 內部靜態類方式(推薦!)

關鍵思路:全域靜態成員放在內部類中,只有該內部類被引用時才執行個體化,以達到延遲執行個體化的目的。這是個完美方案:

  • 確保延遲執行個體化至 getInstance() 的調用;

  • 無需加鎖,效能佳;

  • 不受 JDK 版本限制。

/** * @author: kefeng.wang * @date: 2016-06-07 10:21 **/public class Singleton {    private static class InstanceHolder { // 消極式載入執行個體        private static Singleton instance = new Singleton();    }    private Singleton() {    }    public static Singleton getInstance() {        return InstanceHolder.instance;    }    public void method() {        System.out.println("method() OK.");    }}

2.1.2 產生器(Builder)

將對象的建立過程,封裝到一個產生器對象中,客戶按步驟調用它完成建立。
Java 實現請參考 StringBuilder 的源碼,這裡給出其使用效果:

StringBuilder sb = new StringBuilder();sb.append("Hello world!").append(123).append('!');System.out.println(sb.toString());

2.1.3 簡單工廠(Simple Factory) ★

不是真正的“設計模式”。自身是工廠實作類別,直接提供建立方法(可多個),可以是靜態方法。JDK 中有 Boolean.valueOf(String)Class.forName(String)

/** * @author: kefeng.wang * @date: 2016-06-09 19:42 **/public class DPC3_SimpleFactoryPattern {    private static class SimpleFactory {        public CommonProduct createProduct(int type) { // Factory 方法,返回“產品”介面,形參可無            if (type == 1) {                return new CommonProductImplA(); // 產品具體類            } else if (type == 2) {                return new CommonProductImplB();            } else if (type == 3) {                return new CommonProductImplC();            } else {                return null;            }        }    }    private static class SimpleFactoryClient {        private SimpleFactory factory = null;        public SimpleFactoryClient(SimpleFactory factory) {            this.factory = factory;        }        public final void run() {            CommonProduct commonProduct1 = factory.createProduct(1);            CommonProduct commonProduct2 = factory.createProduct(2);            CommonProduct commonProduct3 = factory.createProduct(3);            System.out.println(commonProduct1 + ", " + commonProduct2 + ", " + commonProduct3);        }    }    public static void main(String[] args) {        SimpleFactory factory = new SimpleFactory(); // 工廠執行個體        new SimpleFactoryClient(factory).run(); // 傳入客戶類    }}

2.1.4 抽象工廠(Abstract factory) ★

一個抽象類別,定義建立對象的抽象方法。繼承後的多個實作類別中,實現建立對象的方法。
用戶端靈活選擇實作類別,完成對象的建立。
JDK 中採用此模式的有 NumberFormat.getInstance()

2.1.5 Factory 方法(Factory method) ★

建立方法的對於抽象類別和實作類別的分工,與“抽象工廠”類似。
區別在於:本模式無需用戶端,自身方法即可完成對象建立前後的操作。

2.1.6 原型(Prototype)

當建立執行個體的過程很複雜或很昂貴時,可通過複製實現。比如 Java 的 Object.clone()

2.2 結構型(Structural Patterns)

用於類或對象的組合關係。

2.2.1 適配器(Adapter)

將一個介面適配成期望的另一個介面,可以消除介面不匹配所造成的相容性問題。
比如把 Enumeration<E> 適配成 Iterator<E>Arrays.asList()T[] 適配成 List<T>

2.2.2 橋接(Bridge) ★

事物由多個因子組合而成,而每個因子都有一個抽象類別和多個實作類別,最終這多個因子可以自由組合。
比如多種遙控器+多種電視機、多種車型+多種路況+多種駕駛員。JDK 中的 JDBCAWT

2.2.3 組合(Composite) ★

把對象的“部分/整體”以樹形結構組織,以便統一對待單個對象或多個對象組合。
比如多級菜單、二叉樹等。

2.2.4 裝飾(Decorator)

運行時動態地將職責附加到裝飾者上。
擴充功能有兩種方式,類繼承是編譯時間靜態決定,而裝飾者模式是運行時動態決定,有獨特優勢。
比如 StringReaderLineNumberReader 裝飾後,為字元流擴充出了 line 相關介面。

2.2.5 外觀(Facade) ★

提供了一個統一的高層介面,用來訪問子系統中的一群介面,讓子系統更容易使用。
比如電腦的啟動(或關閉),是調用CPU/記憶體/磁碟各自的啟動(或關閉)介面。

2.2.6 享元 / 蠅量(Flyweight)

運用共用技術有效地支援大量細粒度的對象。
比如文本處理器,無需為每個字元的多次出現而產生多個字形對象,而是外部資料結構中同一字元的多次出現共用一個字形對象。
JDK 中的 Integer.valueOf(int) 就採用此模式。

2.2.7 代理(Proxy)

proxy 建立並持有 subject 的引用,client 調用 proxy 時,proxy 會轉寄給 subject。
比如 Java 裡的 Collections 集合視圖、RMI/RPC 遠程調用、緩衝代理、防火牆代理等。

2.3 行為型(Behavioral Patterns)

用於類或對象的調用關係。

2.3.1 責任鏈(Chain of responsibility)

一個請求沿著一條鏈傳遞,直到該鏈上的某個處理者處理它為止。
比如 SpringMVC 中的過濾器。

2.3.2 命令(Command)

將命令封裝為對象,可以隨意儲存/載入、傳遞、執行/撤消、排隊、記錄日誌等,將“動作的要求者”從“動作的執行者”中解耦。
參與方包括 Invoker(調用者) => Command(命令) => Receiver(執行者)。
比如定時任務、線程任務 Runnable

2.3.3 解譯器模式(Interpreter)

用於建立簡易的語言解譯器,可處理指令碼語言和程式設計語言,為每個規則建立一個類。
比如 JDK 中的 java.util.Patternjava.text.Format

2.3.4 迭代器(Iterator)

提供一種方法,順序訪問一個彙總對象中的各個元素,而無需暴露其內部表現。
比如 JDK 中的 java.util.Iteratorjava.util.Enumeration

2.3.5 中介者(Mediator)

使用一個中介對象,封裝一系列的對象互動,中介對象使各對象無需顯式相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。
比如 JDK 中的 java.util.Timerjava.util.concurrent.ExecutorService.submit()

2.3.6 備忘錄(Memento)

備忘錄對象用來儲存另一個對象的內部狀態的快照,並可在外部儲存起來,之後可還原到當初的狀態。比如 Java 序列化。
比如 JDK 中的 java.util.Datejava.io.Serializable

2.3.7 觀察者(Observer)

對象間一對多的依賴,被觀察者狀態改變時,觀察者都會收到通知。
參與方包括 Observable(被觀察者) / Observer(觀察者)。
比如 RMI 中的事件、java.util.EventListener

2.3.8 狀態(State)

對象的內部狀態變化時,其行為也隨之改變。其內部實現是,定義一個狀態父類,為每種狀態擴充出狀態子類,當對象內部狀態變化時,所選的狀態子類也跟著切換,但外部只需與該對象互動,而不知道狀態子類的存在。
比如視頻播放器的停止/播放/暫停等狀態。

2.3.9 策略(Strategy)

定義一組演算法,分別封裝起來,獨立於客戶之外,演算法更改時不影響客戶使用。
比如遊戲中的不同角色,可以使用各種裝備,這些裝備可以策略的方式封裝起來。
比如 JDK 中的 java.util.Comparator#compare()

2.3.10 模板方法(Template method) ★

抽象類別中定義頂級的邏輯架構(叫做“模板方法”),一些步驟(可以建立執行個體或其他動作)延遲到子類實現,自身可獨立運轉。
當子類實現的操作是建立執行個體時,模板方法就變成了Factory 方法模式,所以說Factory 方法是特殊的模板方法。

2.3.11 訪問者(Visitor) ★

在不修改被訪問者資料結構的前提下,訪問者中封裝訪問操作,關鍵點是被訪問者中提供被訪問的介面。
適用情境是被訪問者穩定但訪問者靈活多變,或者訪問者有多種不同類的操作。

2.4 複合模式(Compound)

結合兩個或更多模式,組成一個解決方案,解決經常發生的一般性問題。
使用案例:MVC模式(Model/View/Controller),使用了觀察者(Observer)、策略(Strategy)、組合(Composite)、工廠(Factory)、裝飾器(Decorator)等模式。
使用案例:家用電器=介面+資料+邏輯控制,商場=店面+倉庫+邏輯控制。

3 參考文檔

維基百科: 設計模式
Wikipedia: Software design pattern
TutorialsPoint: Design Pattern

設計模式是在不斷出現的特定情境下,針對特定問題,可以重複使用的特定解決模式(套路)。本文按照建立型、結構型、行為型三大類,總結了常見的 24 種設計模式的使用要點,包括適用情境、解決方案、及其相應的 Java 實現。
相關文章:

Java常用設計模式詳解-原廠模式

詳解備忘錄模式及其在Java設計模式編程中的實現

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.