常見的設計模式筆記(簡單工廠模式、單例模式)

原文鏈接:https://www.jianshu.com/p/e55fbddc071c

設計模式筆記

簡單工廠模式

  1. 優點

    • 將創建實例的工作與使用實例的工作分開,使用者不必關心類對象如何創建,實現瞭解耦;
    • 把初始化實例時的工作放到工廠裏進行,使代碼更容易維護。 更符合面向對象的原則 & 面向接口編程,而不是面向實現編程。
  2. 缺點

    • 工廠類集中了所有實例(產品)的創建邏輯,一旦這個工廠不能正常工作,整個系統都會受到影響;
    • 違背“開放 - 關閉原則”,一旦添加新產品就不得不修改工廠類的邏輯,這樣就會造成工廠邏輯過於複雜。
    • 簡單工廠模式由於使用了靜態工廠方法,靜態方法不能被繼承和重寫,會造成工廠角色無法形成基於繼承的等級結構。

實例步驟:

  1. 創建抽象產品類 & 定義具體產品的公共接口;
abstract class Product {
    public abstract void Show();
}
  1. 創建具體產品類(繼承抽象產品類) & 定義生產的具體產品;
class  ProductA extends  Product{

    @Override
    public void Show() {
        System.out.println("生產出了產品A");
    }
}

class  ProductB extends  Product{

    @Override
    public void Show() {
        System.out.println("生產出了產品B");
    }
}

class  ProductC extends  Product{

    @Override
    public void Show() {
        System.out.println("生產出了產品C");
    }
}
  1. 創建工廠類,通過創建靜態方法根據傳入不同參數從而創建不同具體產品類的實例;
public class Factory {
    public static Product Manufacture(String ProductName){
//工廠類裏用switch語句控制生產哪種商品;
//使用者只需要調用工廠類的靜態方法就可以實現產品類的實例化。
        switch (ProductName){
            case "A":
                return new ProductA();

            case "B":
                return new ProductB();

            case "C":
                return new ProductC();

            default:
                return null;

        }
    }

}
  1. 外界通過調用工廠類的靜態方法,傳入不同參數從而創建不同具體產品類的實例
public class SimpleFactoryPattern {
    public static void main(String[] args) {
        //客戶要產品A
        try {
            //調用工廠類的靜態方法 & 傳入不同參數從而創建產品實例
            Factory.Manufacture("A").Show();
        }catch (NullPointerException e){
            System.out.println("沒有這一類產品");
        }

        //客戶要產品B
        try {
            Factory.Manufacture("B").Show();
        }catch (NullPointerException e){
            System.out.println("沒有這一類產品");
        }

        //客戶要產品C
        try {
            Factory.Manufacture("C").Show();
        }catch (NullPointerException e){
            System.out.println("沒有這一類產品");
        }

        //客戶要產品D
        try {
            Factory.Manufacture("D").Show();
        }catch (NullPointerException e){
            System.out.println("沒有這一類產品");
        }
    }
}

結果輸出:

生產出了產品A
生產出了產品C
生產出了產品C
沒有這一類產品

單例模式

  1. 實例引入
    背景:小成有一個塑料生產廠,但裏面只有一個倉庫。
    目的:想用代碼來實現倉庫的管理
    現有做法: 建立倉庫類和工人類
    其中,倉庫類裏的quantity=商品數量;工人類裏有搬運方法MoveIn(int i)和MoveOut(int i)。

出現的問題:通過測試發現,每次工人搬運操作都會新建一個倉庫,就是貨物都不是放在同一倉庫,這是怎麼回事呢?(看下面代碼)

//倉庫類
class StoreHouse {
    private int quantity = 100;

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public int getQuantity() {
        return quantity;
    }
}

//搬貨工人類
class Carrier{
    public StoreHouse mStoreHouse;
    public Carrier(StoreHouse storeHouse){
        mStoreHouse = storeHouse;
    }
    //搬貨進倉庫
    public void MoveIn(int i){
        mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);
    }
    //搬貨出倉庫
    public void MoveOut(int i){
        mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);
    }
}

//工人搬運測試
public class SinglePattern {
    public static void main(String[] args){
        StoreHouse mStoreHouse1 = new StoreHouse();
        StoreHouse mStoreHouse2 = new StoreHouse();
        Carrier Carrier1 = new Carrier(mStoreHouse1);
        Carrier Carrier2 = new Carrier(mStoreHouse2);

        System.out.println("兩個是不是同一個?");

        if(mStoreHouse1.equals(mStoreHouse2)){
            System.out.println("是同一個");
        }else {
            System.out.println("不是同一個");
        }
        //搬運工搬完貨物之後出來彙報倉庫商品數量
        Carrier1.MoveIn(30);
        System.out.println("倉庫商品餘量:"+
        	Carrier1.mStoreHouse.getQuantity());
        Carrier2.MoveOut(50);
        System.out.println("倉庫商品餘量:"+
        	Carrier2.mStoreHouse.getQuantity());
    }
}

結果:

兩個是不是同一個?
不是同一個
倉庫商品餘量:130
倉庫商品餘量:50

單例模式介紹

  1. 模式說明
    實現1個類只有1個實例化對象 & 提供一個全局訪問點
  2. 作用(解決的問題)
    保證1個類只有1個對象,降低對象之間的耦合度
    從上面可看出:工人類操作的明顯不是同一個倉庫實例,而全部工人希望操作的是同一個倉庫實例,即只有1個實例
  3. 工作原理
    在Java中,我們通過使用對象(類實例化後)來操作這些類,類實例化是通過它的構造方法進行的,要是想實現一個類只有一個實例化對象,就要對類的構造方法下功夫:

單例模式的一般實現:(含使用步驟)

public class Singleton {
//1. 創建私有變量 ourInstance(用以記錄 Singleton 的唯一實例)
//2. 內部進行實例化
    private static Singleton ourInstance  = new  Singleton();

//3. 把類的構造方法私有化,不讓外部調用構造方法實例化
    private Singleton() {
    }
//4. 定義公有方法提供該類的全局唯一訪問點
//5. 外部通過調用getInstance()方法來返回唯一的實例
    public static  Singleton newInstance() {
        return ourInstance;
    }
}

好了,單例模式的介紹和原理應該瞭解了吧?那麼我們現在來解決上面小成出現的“倉庫不是一個”的問題吧!

實例講解

改善上面例子的代碼:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//單例倉庫類
class StoreHouse {

    //倉庫商品數量
    private int quantity = 100;
    //自己在內部實例化
    private static StoreHouse ourInstance  = new StoreHouse();;
    //讓外部通過調用getInstance()方法來返回唯一的實例。
    public static StoreHouse getInstance() {
        return ourInstance;
    }

    //封閉構造函數
    private StoreHouse() {
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public int getQuantity() {
        return quantity;
    }
}


//搬貨工人類
class Carrier{
    public StoreHouse mStoreHouse;
    public Carrier(StoreHouse storeHouse){
        mStoreHouse = storeHouse;
    }
    //搬貨進倉庫
    public void MoveIn(int i){
        mStoreHouse.setQuantity(mStoreHouse.getQuantity()+i);
    }
    //搬貨出倉庫
    public void MoveOut(int i){
        mStoreHouse.setQuantity(mStoreHouse.getQuantity()-i);
    }
}

//工人搬運測試
public class SinglePattern {
    public static void main(String[] args){
        StoreHouse mStoreHouse1 = StoreHouse.getInstance();
        StoreHouse mStoreHouse2 = StoreHouse.getInstance();
        Carrier Carrier1 = new Carrier(mStoreHouse1);
        Carrier Carrier2 = new Carrier(mStoreHouse2);

        System.out.println("兩個是不是同一個?");

        if(mStoreHouse1.equals(mStoreHouse2)){
            System.out.println("是同一個");
        }else {
            System.out.println("不是同一個");
        }
        //搬運工搬完貨物之後出來彙報倉庫商品數量
        Carrier1.MoveIn(30);
        System.out.println("倉庫商品餘量:"+Carrier1.mStoreHouse.getQuantity());
        Carrier2.MoveOut(50);
        System.out.println("倉庫商品餘量:"+Carrier2.mStoreHouse.getQuantity());
    }
}

結果:

兩個是不是同一個?
是同一個
倉庫商品餘量:130
倉庫商品餘量:80

從結果分析,使用了單例模式後,倉庫類就只有一個倉庫實例了,再也不用擔心搬運工人進錯倉庫了!!!

特點

  1. 優點
    提供了對唯一實例的受控訪問;
    由於在系統內存中只存在一個對象,因此可以節約系統資源,對於一些需要頻繁創建和銷燬的對象單例模式無疑可以提高系統的性能;
    可以根據實際情況需要,在單例模式的基礎上擴展做出雙例模式,多例模式;
  2. 缺點
    單例類的職責過重,裏面的代碼可能會過於複雜,在一定程度上違背了“單一職責原則”。
    如果實例化的對象長時間不被利用,會被系統認爲是垃圾而被回收,這將導致對象狀態的丟失。

單例模式的實現方式

餓漢式: 初始化單例類時 即 創建單例

具體實現

class Singleton {

    // 1. 加載該類時,單例就會自動被創建
    private static  Singleton ourInstance  = new  Singleton();
    
    // 2. 構造函數 設置爲 私有權限
    // 原因:禁止他人創建實例 
    private Singleton() {
    }
    
    // 3. 通過調用靜態方法獲得創建的單例
    public static  Singleton newInstance() {
        return ourInstance;
    }
}

應用場景: 除了初始化單例類時 即 創建單例外,繼續延伸出來的是:單例對象 要求初始化速度快 & 佔用內存小

懶漢式:按需、延遲創建單例

具體實現

class Singleton {
    // 1. 類加載時,先不自動創建單例
   //  即,將單例的引用先賦值爲 Null
    private static  Singleton ourInstance  = null;

    // 2. 構造函數 設置爲 私有權限
    // 原因:禁止他人創建實例 
    private Singleton() {
    }
    
    // 3. 需要時才手動調用 newInstance() 創建 單例   
    public static  Singleton newInstance() {
    // 先判斷單例是否爲空,以避免重複創建
    if( ourInstance == null){
        ourInstance = new Singleton();
        }
        return ourInstance;
    }
}

缺點 基礎實現的懶漢式是線程不安全的
下面,將對懶漢式 進行優化,使得適合在多線程環境下運行

同步鎖(懶漢式的改進)

原理
使用同步鎖 synchronized鎖住 創建單例的方法 ,防止多個線程同時調用,從而避免造成單例被多次創建
即,getInstance()方法塊只能運行在1個線程中
若該段代碼已在1個線程中運行,另外1個線程試圖運行該塊代碼,則 會被阻塞而一直等待
而在這個線程安全的方法裏我們實現了單例的創建,保證了多線程模式下 單例對象的唯一性
具體實現

// 寫法1
class Singleton {
    // 1. 類加載時,先不自動創建單例
    //  即,將單例的引用先賦值爲 Null
    private static  Singleton ourInstance  = null;
    
    // 2. 構造函數 設置爲 私有權限
    // 原因:禁止他人創建實例 
    private Singleton() {
    }
    
// 3. 加入同步鎖
public static synchronized Singleton getInstance(){
        // 先判斷單例是否爲空,以避免重複創建
        if ( ourInstance == null )
            ourInstance = new Singleton();
        return ourInstance;
    }
}
// 寫法2
// 該寫法的作用與上述寫法作用相同,只是寫法有所區別
class Singleton{ 

    private static Singleton instance = null;

    private Singleton(){
}

    public static Singleton getInstance(){
        // 加入同步鎖
        synchronized(Singleton.class) {
            if (instance == null)
                instance = new Singleton();
        }
        return instance;
    }
}

缺點
每次訪問都要進行線程同步(即 調用synchronized鎖),造成過多的同步開銷(加鎖 = 耗時、耗能)
實際上只需在第1次調用該方法時才需要同步,一旦單例創建成功後,就沒必要進行同步

雙重校驗鎖(懶漢式的改進)

原理
在同步鎖的基礎上,添加1層 if判斷:若單例已創建,則不需再執行加鎖操作就可獲取實例,從而提高性能

具體實現

class Singleton {
    private static  Singleton ourInstance  = null;

    private Singleton() {
    }
    
    public static  Singleton newInstance() {
     // 加入雙重校驗鎖
    // 校驗鎖1:第1個if
    if( ourInstance == null){  // ①
     synchronized (Singleton.class){ // ②
      // 校驗鎖2:第2個 if
      if( ourInstance == null){
          ourInstance = new Singleton();
          }
      }
  }
        return ourInstance;
   }
}


// 說明
// 校驗鎖1:第1個if
// 作用:若單例已創建,則直接返回已創建的單例,無需再執行加鎖操作
// 即直接跳到執行 return ourInstance

// 校驗鎖2:第2個 if 
// 作用:防止多次創建單例問題
// 原理
  // 1. 線程A調用newInstance(),當運行到②位置時,此時線程B也調用了newInstance()
  // 2. 因線程A並沒有執行instance = new Singleton();,此時instance仍爲空,因此線程B能突破第1層 if 判斷,運行到①位置等待synchronized中的A線程執行完畢
  // 3. 當線程A釋放同步鎖時,單例已創建,即instance已非空
  // 4. 此時線程B 從①開始執行到位置②。此時第2層 if 判斷 = 爲空(單例已創建),因此也不會創建多餘的實例

缺點
實現複雜 = 多種判斷,易出錯

靜態內部類

原理
根據 靜態內部類 的特性,同時解決了按需加載、線程安全的問題,同時實現簡潔
在靜態內部類裏創建單例,在裝載該內部類時纔會去創建單例
線程安全:類是由 JVM加載,而JVM只會加載1遍,保證只有1個單例
具體實現

class Singleton {
    
    // 1. 創建靜態內部類
    private static class Singleton2 {
       // 在靜態內部類裏創建單例
      private static  Singleton ourInstance  = new Singleton();
    }

    // 私有構造函數
    private Singleton() {
    }
    
    // 延遲加載、按需創建
    public static  Singleton newInstance() {
        return Singleton2.ourInstance;
    }

}

調用過程說明:
1. 外部調用類的newInstance()
2. 自動調用Singleton2.ourInstance

  1. 此時單例類Singleton2得到初始化

  2. 而該類在裝載 & 被初始化時,會初始化它的靜態域,從而創建單例;

  3. 由於是靜態域,因此只會JVM只會加載1遍,Java虛擬機保證了線程安全性

  4. 最終只創建1個單例

發佈了33 篇原創文章 · 獲贊 22 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章