設計模式是在不斷出現的特定情境下,針對特定問題,可以重複使用的特定解決模式(套路)。本文依照創建型、結構型、行為型三大類,總結了常見的 24 種設計模式的使用要點,包括適用場景、解決方案、及其對應的 Java 實作。
設計模式,是在某個不斷出現的「情境(Context)」下,針對某個「問題」的某種「解決方案」:
「問題」必須是重複出現的,「解決方案」必須是可重複應用的;
“問題”包含了“一個目標”和“一組約束”,當解決方案在兩者之間取得平衡,才是有用的模式;
一個類別只有一個職責,而不是多個職責耦合在一個類別中(例如介面與邏輯要分離)。
對擴展開放,對修改關閉,使用介面和抽象類別。
父類別可以出現的地方,子類別一定可以出現,這是繼承複用的基石。
低依賴,各實體盡量獨立,盡量減少相互作用。
客戶(client)應該不依賴它不使用的方法。盡量使用多個介面分工合成,而不是單一介面耦合多種功能。
Both should depend upon abstractions.Abstractions shul nottails Details should depend upon abstractions.
要依賴抽象(介面或抽象類別),而不是具體(具體類別)。
設計模式看似簡單問題複雜化。但「簡單」的設計彈性差,在目前專案中不方便擴展,拿到其他專案更是無法使用,相當於「一次性程式碼」。而設計模式的程式碼,結構清晰,目前專案中便於擴展,拿到其他專案也適用,是通用的設計。
許多程式設計師接觸到設計模式之後,都有相見恨晚的感覺,感覺自己脫胎換骨,達到了新的境界,設計模式可以作為程式設計師劃分水平的標準。
不過我們也不能陷入模式的陷阱,為了使用模式而去套模式,那樣會陷入形式主義。
(1)與他人溝通時,提到設計模式名稱,就隱含了其模式;
(2) 使用模式觀察軟體系統,可以保持在設計層次,而不會被停留在瑣碎的物件細節上;
(3) 團隊間用設計模式交流,彼此看法不容易誤解。
非常建議閱讀。英文書名是《Head First Design Patterns》。
信耶穌的人都要讀聖經,信OO(面向對象)的人都要讀四人組的《Head First 設計模式》,官方網站 Head First Design Patterns。 2004 該書榮獲Jolt獎(類似電影領域的奧斯卡獎)。
是首次將模式歸類的功臣,開啟了軟體領域的一大躍進;
模式的範本:包含名稱、類目、意圖、動機、適用性、類別圖、參與者及其協作、結果、實現、範例程式碼、已知應用、相關模式等。
英文書名是《Design Patterns: Elements of Reusable Object-Oriented Software》 。也是四人組所著。
是軟體工程領域有關軟體設計的一本書,提出並總結了對於一些常見軟體設計問題的標準解決方案,稱為軟體設計模式。這本書在1994年10月21日首次出版,至2012年3月已印行40張。
設計模式可分為三個大類,每個大類又包含若干具體的模式。
容易混淆的幾種模式:簡單工廠S / 抽象工廠A / 工廠方法F / 模板方法T
「工廠」字樣的:帶的只用來建立實例,例如S/A/F;不帶的則不限,例如T;
「方法」字樣的:帶的無需額外的客戶端參與,可以獨立運轉,例如F/T;不帶的需要額外的客戶端調用,例如S/A。
#用於物件的創建,把創建物件的工作放在另一個物件中,或延後到子類別中。
確保一個類別只有一個實例,並提供一個全域的存取點。
要注意的是,多個類別載入器下使用單例,會導致各類別載入器下方都有一個單例實例,因為每個類別載入器都有自己獨立的命名空間。
JDK 中的單例有 Runtime.getRuntime()
、NumberFormat.getInstance()
下面總結了四種執行緒安全的 Java 實作方法。每個實作都可以用 Singleton.getInstance().method();
呼叫。
關鍵想法:作為類別的靜態全域變量,載入該類別時實例化。
缺點是真正使用該實例之前(也有可能一直沒用到),就已經實例化,浪費資源。
對於 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."); } }
關鍵想法:在方法 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."); } }
#關鍵想法:不同步的情況下檢查到尚未創建,再同步檢查到尚未實例化時,才實例化。以便大大減少同步的情況。
缺點是:要求 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."); } }
關鍵想法:全域靜態成員放在內部類別中,只有該內部類別被引用時才實例化,以達到延遲實例化的目的。這是個完美方案:
確保延遲實例化至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."); } }
#將物件的建立過程,封裝到一個生成器物件中,客戶按步驟呼叫它完成建立。
Java 實作請參考StringBuilder
的來源碼,這裡給出其使用效果:
StringBuilder sb = new StringBuilder(); sb.append("Hello world!").append(123).append('!'); System.out.println(sb.toString());
#不是真正的“設計模式”。本身是工廠實作類,直接提供創建方法(可多個),可以是靜態方法。 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(); // 传入客户类 } }
#一個抽象類,定義建立物件的抽象方法。繼承後的多個實作類別中,實作建立物件的方法。
客戶端靈活選擇實現類,完成物件的建立。
JDK 中採用此模式的有 NumberFormat.getInstance()
。
建立方法的對於抽象類別和實作類別的分工,與「抽象工廠」類似。
差別在於:本模式無需客戶端,自身方法即可完成物件建立前後的操作。
當建立實例的過程很複雜或很昂貴時,可透過複製實作。例如 Java 的 Object.clone()
。
用於類別或物件的組合關係。
將一個介面適配成期望的另一個接口,可以消除接口不匹配所造成的相容性問題。
例如把Enumeration<E>
適配成Iterator<E>
,Arrays.asList()
把T[]
適配成List<T>
。
事物由多個因子組合而成,而每個因子都有一個抽象類別和多個實現類,最終這多個因子可以自由組合。
例如多種遙控器 多種電視機、多種車型 多種路況 多種駕駛者。 JDK 中的 JDBC
和 AWT
。
把物件的「部分/整體」以樹狀結構組織,以便統一對待單一物件或多個物件組合。
例如多層選單、二元樹等。
運行時動態地將職責附加到裝飾者上。
擴充功能有兩種方式,類別繼承是編譯時靜態決定,而裝飾者模式是執行時期動態決定,有獨特優勢。
例如 StringReader
被 LineNumberReader
裝飾後,為字元流擴充了 line
相關介面。
提供了一個統一的高層接口,用來存取子系統中的一群接口,讓子系統更容易使用。
例如電腦的啟動(或關閉),是呼叫CPU/記憶體/磁碟各自的啟動(或關閉)介面。
運用共享技術有效支援大量細粒度的物件。
例如文字處理器,無需為每個字元的多次出現而產生多個字形對象,而是外部資料結構中同一字元的多次出現共用一個字形物件。
JDK 中的 Integer.valueOf(int)
就採用此模式。
proxy 建立並持有 subject 的引用,client 呼叫 proxy 時,proxy 會轉送給 subject。
例如 Java 裡的 Collections
集合視圖、RMI/RPC 遠端呼叫、快取代理、防火牆代理等。
用於類別或物件的呼叫關係。
一個請求沿著一條鏈傳遞,直到該鏈上的某個處理者處理它為止。
例如 SpringMVC 中的過濾器。
將命令封裝為對象,可以隨意儲存/載入、傳遞、執行/撤銷、排隊、記錄日誌等,將“動作的請求者”從「動作的執行者」解耦。
參與方包括 Invoker(呼叫者) => Command(命令) => Receiver(執行者)。
例如定時任務、執行緒任務 Runnable
。
用於建立簡易的語言解釋器,可處理腳本語言和程式語言,為每個規則建立一個類別。
例如 JDK 中的 java.util.Pattern
、java.text.Format
。
提供一個方法,順序存取一個聚合物件中的各個元素,而無需暴露其內部表現。
例如 JDK 中的 java.util.Iterator
和 java.util.Enumeration
。
使用一個中介對象,封裝一系列的對象交互,中介對象使各對象無需顯式相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的交互作用。
例如 JDK 中的 java.util.Timer
和 java.util.concurrent.ExecutorService.submit()
。
備忘錄物件用來儲存另一個物件的內部狀態的快照,並可在外部儲存起來,之後可還原到當初的狀態。例如 Java 序列化。
例如 JDK 中的 java.util.Date
和 java.io.Serializable
。
物件間一對多的依賴,被觀察者狀態改變時,觀察者都會收到通知。
參與者包括 Observable(被觀察者) / Observer(觀察者)。
例如 RMI 中的事件、java.util.EventListener
。
物件的內部狀態變化時,其行為也隨之改變。其內部實作是,定義一個狀態父類,為每種狀態擴展出狀態子類,當物件內部狀態變化時,所選的狀態子類別也跟著切換,但外部只需與該物件交互,而不知道狀態子類別的存在。
例如影片播放器的停止/播放/暫停等狀態。
定義一組演算法,分別封裝起來,獨立於客戶之外,演算法變更時不影響客戶使用。
例如遊戲中的不同角色,可以使用各種裝備,這些裝備可以策略的方式封裝起來。
例如 JDK 中的 java.util.Comparator#compare()
。
抽象類別中定義頂級的邏輯框架(叫做「模板方法」),一些步驟(可以建立實例或其他操作)延遲到子類實現,自身可獨立運作。
當子類別實現的操作是創建實例時,模板方法就變成了工廠方法模式,所以說工廠方法是特殊的模板方法。
在不修改被訪客資料結構的前提下,訪客中封裝存取操作,關鍵點是被訪客中提供被訪問的接口。
適用場景是被訪客穩定但訪客靈活多變,或訪客有多種不同類別的操作。
結合兩個或更多模式,組成一個解決方案,解決經常發生的一般性問題。
使用案例:MVC模式(Model/View/Controller),使用了觀察者(Observer)、策略(Strategy)、組合(Composite)、工廠(Factory)、裝飾器(Decorator)等模式。
使用案例:家用電器=介面+資料+邏輯控制,商場=店面+倉庫+邏輯控制。
維基百科: 設計模式
Wikipedia: Software design pattern
TutorialsPoint: Design Pattern
設計模式是在不斷出現的特定情境下,針對特定問題,可以重複使用的特定解決模式(套路)。本文依照創建型、結構型、行為型三大類,總結了常見的 24 種設計模式的使用要點,包括適用場景、解決方案、及其對應的 Java 實作。
相關文章:
以上是總結常見的 24 種設計模式的使用要點及其 Java 實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!