# 設計模式 ###### tags: `java` `design pattern` ## 目錄 - [簡介](#簡介) - [單例模式](#單例模式) ## 簡介 設計模式是指在軟體發展中,已經經過驗證來解決在特定的環境下、重複出現的或是特定問題的解決方案。 設計模式並不能解決所有問題,而只是解決「特定問題」,在這些問題「重複出現」的情況下,重用設計模式就可以解決那些特定問題;但設計模式並不是萬靈丹,也要避免過度使用、過度設計造成的軟體的架構過於複雜。 設計模式也無法直接用來完成程式碼的編寫,而是描述在各種不同情況下,要怎麼解決問題的一種方案。 設計模式最有名的莫過於GoF合作出版的「設計模式:可復用物件導向軟體的基礎」一書裡面的23種設計模式,至今都被奉為聖經般存在。 > **補充** > > GoF(英語,Gang of Four,簡稱GoF),Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides四個人 合作出版了「設計模式:可復用物件導向軟體的基礎」一書。 但該書作為聖經其實頗為艱深並不容易閱讀,後來市面上其實也出了很多關於設計模式的書籍,就比較像是「聖經」的「解說版」,會比較容易理解,建議初學者可以先看市面上的其他書籍。 GoF將23種設計模式分成了三大類: - **一、創建型模式(Creational)** 根據合適的情況來建立物件。單純的建立物件常會導致一些設計問題或增加設計的複雜度。創建型模式則藉由控制物件的生成方式來解決這問題。 | 設計模式 | 目的 | 生活案例 | 框架源碼舉例 | | :----------------------------- | :--------------- | :------- | :----------------------------------- | | 工廠模式(Factory) | 封裝創建細節 | 實體工廠 | LoggerFactory、Calender | | 單例模式(Singleton) | 保證獨一無二 | CEO | BeanFactory、Runtime | | 原型模式(Prototype) | 高效創建對象 | 克隆 | ArrayList、PrototypeBean | | 建造者模式(Builder) | 開放個性配置步驟 | 選配 | StringBuilder、BeanDefinationBuilder | - **二、結構型模式(Structural)** 適合不同情境下的物件間關係結構,藉由從上到下的方式來了解元件間的關係,以簡化設計。 | 設計模式 | 目的 | 生活案例 | 框架源碼舉例 | | :---------------------- | :--------------------------------------------- | :----------- | :-------------------------------------------------------- | | 代理模式(Proxy) | 增強職責 | 媒婆 | ProxyFactoryBean、JdkDynamicAopProxy、CglibAopProxy | | 門面模式(Facade) | 統一訪問入口 | 前台、導診台 | JdbcUtils、RequestFacade | | 裝飾器模式(Decorator) |靈活擴展、同宗同源 | 煎餅 | BufferedReader、InputStream | | 享元模式(Flyweight) |共享資源池 | 全國社保聯網 | String、Integer、ObjectPool | | 組合模式(Composite) | 統一整體和個體 | 組織架構樹 | HashMap、SqlNode | | 適配器模式(Adapter) | 兼容轉換 | 電源適配 | AdvisorAdapter(Spring AoP)、HandlerAdapter(SpringMVC) | | 橋接模式(Bridge) | 不允許用繼承 | 橋 | DriverManager | - **三、行為型模式(Behavioral)** 物件之間的合作行為構成的程式行為,物件之間若有設計良好的行為互動,可以使得程式執行時更有效率,且職責更為清晰、整個程式的結構將更有彈性。 | 設計模式 | 目的 | 生活案例 | 框架源碼舉例 | | :------------------------------------ | :--------------------- | :------------------- | :---------------------------------------- | | 委派模式(Delegate) | 只對結果負責 | 授權委託書 | ClassLoader、BeanDefinitionParserDelegate | | 模板模式(Template) | 邏輯復用 | 把大象裝進冰箱的步驟 | JdbcTemplate、HttpServlet | | 策略模式(Strategy) | 選擇支付方式 | Comparator、InstantiationStrategy | | 責任鏈模式(Chain of Responsibility) | 解耦處理邏輯 | 踢皮球 | FilterChain、Pipeline | | 迭代器模式(Iterator) | 統一對集合的訪問方式 | 統一刷臉進站 | Iterator | | 命令模式(Command) | 解耦請求和處理 | 遙控器 | Runnable、TestCase | | 狀態模式(State) | 綁定狀態和行為 | 訂單狀態跟蹤 | Lifecycle | | 備忘錄模式(Memento) | 備份 | 草稿箱 | StateManageableMessageContext | | 中介者模式(Mediator) | 統一管理網狀資源 | 朋友圈 | Timer | | 解釋器模式(Interpreter) | 實現特定語法解析 | 摩斯密碼 | Pattern、ExpressionParser | | 觀察者模式(Observer) | 解耦觀察者與被觀察者 | 鬧鐘 | ContextLoaderListener | | 訪問者模式(Visitor) | 解耦數據結構和數據操作 | KPI考核 | FileVisitor、BeanDefinitionVisitor | ## 單例模式 單例模式(Singleton),其定義為:==只有一個實例,而且自行實例化並向整個系統提供這個實例。== - 創建型模式 - 必須要創建自己的實例 - 需確保只有一個對象被創建 - 該類別提供一個方法取得被創建的唯一物件 > **補充** > > 適合場景為存取IO和資料庫等資源時,適合使用該模式,例如像資料庫連線通常只要一個就夠了,多個連線容易造成資料庫負擔,或是在同一個程式內,過多的資料庫連線,也會造成系統資源的負擔。 ### 建立單例模式 ##### 積極模式 ``` public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } } ``` 積極模式在宣告靜態物件的時候就已經初始化。 ##### 懶散模式(Lazy) ``` public class Singleton{ private static Singleton instance; //私有的建構式讓別人不能創造 private Singleton (){} public static Singleton getInstance(){ if (instance == null) { instance = new Singleton(); } return instance; } } ``` 懶散模式(Lazy)在呼叫getInstance時才進行初始化,且非執行緒安全,可以在getInstance()方法上加上synchronized來達到執行緒安全,但是這樣做的話效率會很低。 ##### 雙重鎖模式 ``` public class Singleton { private volatile static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ // 第一層判斷為了避免不必要的同步 if(instance == null){ synchronized (Singleton.class){ // 第二層判斷為了在null的狀況下建立實例 if(instance == null){ instance = new Singleton(); } } } return instance; } } ``` ```java instance = new Singleton(); ``` 上面這段程式碼看起來只有一段,但其實不是原子操作,這句程式碼會被編譯成多條組合指令,大致上做了三件事: 1. 給Singleton的實例分配記憶體; 2. 呼叫Singleton的建構函數,初始化成員欄位; 3. 將instance物件指向分配的記憶體空間(此時instance不是null)。 但是由於Java編譯器允許失序執行,所以 2. 和 3. 的順序是無法保證的,有可能 1-2-3 也有可能 1-3-2 。如果在 3. 執行完畢、2. 還沒執行之前,切換到線程B,那instance已經不是null,此時B取走instance再使用就會出錯。 ##### 靜態內部類別模式 ``` public class StaticInnerClass { private StaticInnerClass(){} public static StaticInnerClass getInstance(){ return StaticInnerClassHolder.instance; } /** * 靜態的內部類別 */ private static class StaticInnerClassHolder{ private static StaticInnerClass instance = new StaticInnerClass(); } } ``` 可以確保線程安全,保證物件唯一性,並且延遲實例化,所以推薦使用。 ##### 列舉模式 ``` public enum Singleton { INSTANCE; public void doSomeThing() { } } ``` 可讀性並不是很高,較少使用。 ## 2021-09-04 單例模式 ``` class RemoteDatabase { private static RemoteDatabase instance = null; private RemoteDatabase() { } public static RemoteDatabase getInstance() { if(instance == null) { instance = new RemoteDatabase(); } return instance; } public boolean connect(String ip) { int result = (int)(Math.random() * 2); return result == 0 ? true : false; } public void close() { System.out.println("Connection closed."); } } public class App { public static void main(String[] argc) throws InterruptedException { RemoteDatabase remoteDatabase = RemoteDatabase.getInstance(); System.out.println("資料庫連線...."); Thread.sleep(1000); // 延遲1秒鐘 if(remoteDatabase.connect("192.168.0.1") == true) System.out.println("連線成功"); else System.out.println("連線失敗"); System.out.println("資料庫操作...."); Thread.sleep(1000); // 延遲1秒鐘 remoteDatabase.close(); } } ```
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up