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

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

php是最好的语言
php是最好的语言原創
2018-08-06 14:09:302938瀏覽

設計模式是在不斷出現的特定情境下,針對特定問題,可以重複使用的特定解決模式(套路)。本文依照創建型、結構型、行為型三大類,總結了常見的 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 shul nottails 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 / 工廠方法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) { // 工厂方法,返回“产品”接口,形参可无
            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 method) ★

建立方法的對於抽象類別和實作類別的分工,與「抽象工廠」類似。
差別在於:本模式無需客戶端,自身方法即可完成物件建立前後的操作。

2.1.6 原型(Prototype)

當建立實例的過程很複雜或很昂貴時,可透過複製實作。例如 Java 的 Object.clone()

2.2 結構型(Structural Patterns)

用於類別或物件的組合關係。

2.2.1 適配器(Adapter)

將一個介面適配成期望的另一個接口,可以消除接口不匹配所造成的相容性問題。
例如把Enumeration1a4db2c2c2313771e5742b6debf617a1 適配成Iterator1a4db2c2c2313771e5742b6debf617a1Arrays.asList()T[]適配成List8742468051c85b06f0a0af9e3e506b5c

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) ★

抽象類別中定義頂級的邏輯框架(叫做「模板方法」),一些步驟(可以建立實例或其他操作)延遲到子類實現,自身可獨立運作。
當子類別實現的操作是創建實例時,模板方法就變成了工廠方法模式,所以說工廠方法是特殊的模板方法。

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設計模式程式設計中的實作

以上是總結常見的 24 種設計模式的使用要點及其 Java 實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn