設計模式

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;
    }
}
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();
    }
}