架構師內功心法:設計模式(二)創建型模式(二)

寫在前面:

  • 你好,歡迎關注!
  • 我熱愛技術,熱愛分享,熱愛生活, 我始終相信:技術是開源的,知識是共享的!
  • 博客裏面的內容大部分均爲原創,是自己日常的學習記錄和總結,便於自己在後面的時間裏回顧,當然也是希望可以分享 自己的知識。如果你覺得還可以的話不妨關注一下,我們共同進步!
  • 個人除了分享博客之外,也喜歡看書,寫一點日常雜文和心情分享,如果你感興趣,也可以關注關注!
  • 公衆號:傲驕鹿先生

目錄

四、 建造者模式(Build Pattern)

五、 單例模式(Singleton Pattern)

六、 原型模式( Prototype Pattern)


四、 建造者模式(Build Pattern

建造者模式(builder)是創建一個複雜對象的創建型模式,將構建複雜對象的過程和它的部件解耦,使得構建過程和部件的表示分離開來。
 
1、建造者模式結構圖
  • Director: 指揮者類,用於統一組裝流程
  • Builder:抽象Builder類,規範產品的組建,一般是由子類實現。
  • ConcreteBulider: 抽象Builder類的實現類,實現抽象Builder類定義的所有方法,並且返回一個組建好的對象
  • Product: 產品類
2、建造者模式的簡單實現
這裏我們就用DIY組裝電腦的例子來實現一下建造者模式。
(1)創建產品類
  我要組裝一臺電腦,電腦被抽象爲Computer類,它有三個部件:CPU 、主板和內存。並在裏面提供了三個方法分別用來設置CPU 、主板和內存:
public class Computer {   
    private String mCpu;    
    private String mMainboard;    
    private String mRam;

    public void setmCpu(String mCpu) { 
       this.mCpu = mCpu;
    }

    public void setmMainboard(String mMainboard) {
        this.mMainboard = mMainboard;
    }

    public void setmRam(String mRam) {
        this.mRam = mRam;
    }
}
2)抽象Builder類
商家組裝電腦有一套組裝方法的模版,就是一個抽象的Builder類,裏面提供了安裝CPU、主板和內存的方法,以及組裝成電腦的create方法:
public abstract class Builder {
    public abstract void buildCpu(String cpu);
    public abstract void buildMainboard(String mainboard);
    public abstract void buildRam(String ram);
    public abstract Computer create();
}

(3)具體Builder類

商家實現了抽象的Builder類,MoonComputerBuilder類用於組裝電腦:

public class MoonComputerBuilder extends Builder {
    private Computer mComputer = new Computer();
    @Override    public void buildCpu(String cpu) { 
       mComputer.setmCpu(cpu);
    }

    @Override
    public void buildMainboard(String mainboard) {
        mComputer.setmMainboard(mainboard);
    }

    @Override
    public void buildRam(String ram) {
        mComputer.setmRam(ram);
    }

    @Override
    public Computer create() {
        return mComputer;
    }
}

(4)具體指揮者(Director)類

商家的指揮者類用來規範組裝電腦的流程規範,先安裝主板,再安裝CPU,最後安裝內存並組裝成電腦:
public class Direcror {
    Builder mBuild=null;
    public Direcror(Builder build){
       this.mBuild=build;
    }
    public Computer CreateComputer(String cpu,String mainboard,String ram){
       //規範建造流程
       this.mBuild.buildMainboard(mainboard);
       this.mBuild.buildCpu(cpu);
       this.mBuild.buildRam(ram);
       return mBuild.create();
    }
}

(5)客戶端調用指揮者(Director)類

最後商家用指揮者類組裝電腦。我們只需要提供我們想要的CPU,主板和內存就可以了,至於商家怎樣組裝電腦我們無需知道。
public class CreatComputer {
    public static void main(String[]args){
        Builder mBuilder=new MoonComputerBuilder();
        Direcror mDirecror=new Direcror(mBuilder);
        //組裝電腦
        mDirecror.CreateComputer("i7-6700","華擎玩家至尊","三星DDR4");
    }
}

3、優點和缺點

(1)優點

  • 使用建造者模式可以使客戶端不必知道產品內部組成的細節。

  • 具體的建造者類之間是相互獨立的,容易擴展。

  • 由於具體的建造者是獨立的,因此可以對建造過程逐步細化,而不對其他的模塊產生任何影響。

(2)缺點

  • 產生多餘的Build對象以及Dirextor類。

4、適用場景

  • 當創建複雜對象的算法應該獨立於該對象的組成部分以及它們的裝配方式時。

  • 相同的方法,不同的執行順序,產生不同的事件結果時。

  • 多個部件或零件,都可以裝配到一個對象中,但是產生的運行結果又不相同時。

  • 產品類非常複雜,或者產品類中的調用順序不同產生了不同的效能。

  • 創建一些複雜的對象時,這些對象的內部組成構件間的建造順序是穩定的,但是對象的內部組成構件面臨着複雜的變化。

五、 單例模式(Singleton Pattern

單例模式是確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。實現單例模式有幾個關鍵點:
  1. 構造函數不對外開放---Private
  2. 通過一個靜態方法或者枚舉返回單利對象
  3. 確保單例類的對象有且只有一個,尤其是在多線程環境下。
  4. 確保單例類對象在反序列化時不會重新構建對象。
1、單例模式的簡單實現
單例模式的實現方式有多種,根據需求場景,可分爲2大類、6種實現方式。具體如下:
 
初始化單例類時創建單例
(1)餓漢式
原理: 依賴 JVM類加載機制,保證單例只會被創建1次,即 線程安全
  1. JVM在類的初始化階段(即 在Class被加載後、被線程使用前),會執行類的初始化
  2. 在執行類的初始化期間,JVM會去獲取一個鎖。這個鎖可以同步多個線程對同一個類的初始化
應用場景: 除了初始化單例類時 即 創建單例外,繼續延伸出來的是:單例對象 要求初始化速度快 & 佔用內存小
public class HungrySingleton {
    private static  final HungrySingleton mSingleton= new HungrySingleton();
    //私有構造函數
    private HungrySingleton(){
    }
    //公有的靜態函數,對外暴露獲取單例對象的接口
    public static HungrySingleton getSingleton(){
        return mSingleton;
    }
}

(2)枚舉類型

原理: 滿足單例模式所需的 創建單例、線程安全、實現簡潔的需求
單元素的枚舉類型已經成爲實現 Singleton的最佳方法
public enum Singleton{

    //定義1個枚舉的元素,即爲單例類的1個實例
    INSTANCE;

    // 隱藏了1個空的、私有的 構造方法
    // private Singleton () {}
}
// 獲取單例的方式:
Singleton singleton = Singleton.INSTANCE;
按需、延遲創建單例
(1)懶漢式(基礎實現)
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;
    }
}

基礎實現的懶漢式是線程不安全的,具體原因如下:

(2)懶漢式——同步鎖優化
原理: 使用同步鎖 synchronized鎖住 創建單例的方法 ,防止多個線程同時調用,從而避免造成單例被多次創建
  • 即,getInstance()方法塊只能運行在1個線程中
  • 若該段代碼已在1個線程中運行,另外1個線程試圖運行該塊代碼,則 會被阻塞而一直等待
  • 而在這個線程安全的方法裏我們實現了單例的創建,保證了多線程模式下 單例對象的唯一性
缺點: 每次訪問都要進行線程同步(即 調用synchronized鎖),造成過多的同步開銷(加鎖 = 耗時、耗能)
// 寫法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;
    }
}

(3)懶漢式——雙重鎖優化

原理: 在同步鎖的基礎上,添加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: 第一個if

  • 作用:若單例已創建,則直接返回已創建的單例,無需再執行加鎖操作。即直接跳到執行 return ourInstance
檢驗鎖2:第二個if
  • 作用:防止多次創建單例問題
  • 原理:
    • 線程A調用newInstance(),當運行到②位置時,此時線程B也調用了newInstance()
    • 因線程A並沒有執行instance = new Singleton();,此時instance仍爲空,因此線程B能突破第1層if 判斷,運行到①位置等待synchronized中的A線程執行完畢
    • 當線程A釋放同步鎖時,單例已創建,即instance已非空
    • 此時線程B 從①開始執行到位置②。此時第2層if判斷 = 爲空(單例已創建),因此也不會創建多餘的實例
 
(4)靜態內部類
public class StaticInnerSingleton {
    private StaticInnerSingleton(){}
    public static StaticInnerSingleton getInstance(){
        return InstanceHolder .mStaticInnerSingleton;
    }
    //靜態內部類
    private static class InstanceHolder {
        private static StaticInnerSingleton mStaticInnerSingleton=new StaticInnerSingleton();
    }
}

JVM在類的初始化階段(即在Class被加載後,且被線程使用之前),會執行類的初始化。在執行類的初始化期間,JVM會去獲取一個鎖。這個鎖可以同步多個線程對同一個類的初始化。

由於Java語言是多線程的,多個線程可能在同一時間嘗試去初始化同一個類或接口(比如這裏多個線程可能在同一時刻調用getInstance()方法來初始化InstanceHolder類)。因此,在Java中初始化一個類或者接口時,需要做細緻的同步處理。 Java語言規範規定,對於每一個類或接口C,都有一個唯一的初始化鎖LC與之對應。從C到LC的映射,由JVM的具體實現去自由實現。JVM在類初始化期間會獲取這個初始化鎖,並且每個線程至少獲取一次鎖來確保這個類已經被初始化過了。
 
當第一次加載Singleton類時並不會初始化sInstance,只有在第一次調用Singleton的getInstance方法時纔會導致sInstance被初始化。因此,第一次調用getInstance方法會導致虛擬機加載SingletonHolder類,這種方式不僅能夠確保線程安全,也能夠保證單例對象的唯一性,同時也延遲了單例的實例化
 
(5)使用容器實現單例模式
public class SingletonManager {
    private static Map<String, Object> objectMap = new HashMap<>();

    private SingletonManager() {
    }

    public static void registerService(String key, Object instance) {
        if (!objectMap.containsKey(key)) {
            objectMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objectMap.get(key);
    }
}

在程序的初始,將多種單例類型注入到一個統一的管理類中,在使用時根據key獲取對象對應類型的對象。

這種方式使得我們可以管理多種類型的單例,並且在使用可以通過統一的接口進行獲取操作,降低了用戶使用成本,也對用戶隱藏了具體實現,降低了耦合度
 
反序列化獲得單例
 
在反序列化的情況下他們會出現重新創建對象。構造函數是私有的,反序列化依然可以通過特殊的途徑去創建類的一個新的實例,相當於調用該類的構造函數。反序列化提供了一個特別的鉤子函數,類中具有一個私有的readResolve()函數,這個函數可以讓程序員控制對象的反序列化。如果要杜絕單例對象在被反序列化時重新生成對象,那麼必須加入readResolve函數。
public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID=0L;
    private static final SerializableSingleton INSTANCE =new SerializableSingleton();
    private SerializableSingleton(){}
    public static SerializableSingleton getSerializableSingleton(){
        return INSTANCE;
    }
    private Object readResolve() throws ObjectStreamException{
        return INSTANCE;
    }
}

readResolve方法中將單例對象返回,而不是重新生成一個新的對象。而對於枚舉則不存在這個問題。

六、原型模式( Prototype Pattern

原型模式屬於對象的創建模式。通過給出一個原型對象來指明所有創建的對象的類型,然後用複製這個原型對象的辦法創建出更多同類型的對象。這就是選型模式的用意。
原型模式要求對象實現一個可以“克隆”自身的接口,這樣就可以通過複製一個實例對象本身來創建一個新的實例。這樣一來,通過原型實例創建新的對象,就不再需要關心這個實例本身的類型,只要實現了克隆自身的方法,就可以通過這個方法來獲取新的對象,而無須再去通過new來創建。
原型模式有兩種表現形式:(1)簡單形式(2)登記形式  ;這兩種表現形式僅僅是原型模式的不同實現。
 
1、簡單的原型模式
(1)簡單的原型模式結構圖
這種形式涉及到三個角色:
  (1)客戶(Client)角色:客戶類提出創建對象的請求。
  (2)抽象原型(Prototype)角色:這是一個抽象角色,通常由一個Java接口或Java抽象類實現。此角色給出所有的具體原型類所需的接口。
  (3)具體原型(Concrete Prototype)角色:被複制的對象。此角色需要實現抽象的原型角色所要求的接口。
(2)簡單實現
抽象原型:
public interface Prototype{
    /**
     * 克隆自身的方法
     * @return 一個從自身克隆出來的對象
     */
    public Object clone();
}
具體原型:
public class ConcretePrototype1 implements Prototype {
    public Prototype clone(){
        //最簡單的克隆,新建一個自身對象,由於沒有屬性就不再複製值了
        Prototype prototype = new ConcretePrototype1();
        return prototype;
    }
}

public class ConcretePrototype2 implements Prototype {
    public Prototype clone(){
        //最簡單的克隆,新建一個自身對象,由於沒有屬性就不再複製值了
        Prototype prototype = new ConcretePrototype2();
        return prototype;
    }
}

客戶端角色:

public class Client {

    //持有需要使用的原型接口對象
    private Prototype prototype;
    //構造方法,傳入需要使用的原型接口對象
    public Client(Prototype prototype){
        this.prototype = prototype;
    }
    public void operation(Prototype example){
        //需要創建原型接口的對象
        Prototype copyPrototype = prototype.clone();
    }
}

2、登記形式的原型模式

(1)登記形式的原型模式結構圖

作爲原型模式的第二種形式,它多了一個原型管理器(PrototypeManager)角色,該角色的作用是:創建具體原型類的對象,並記錄每一個被創建的對象。

(2)簡單實現

抽象原型角色:

public interface Prototype{
    public Prototype clone();
    public String getName();
    public void setName(String name);
}

具體原型角色:

public class ConcretePrototype1 implements Prototype {
    private String name;
    public Prototype clone(){
        ConcretePrototype1 prototype = new ConcretePrototype1();
        prototype.setName(this.name);
        return prototype;
    }
    public String toString(){
        return "Now in Prototype1 , name = " + this.name;
    }
    @Override
    public String getName() {
        return name;
    }
    @Override
    public void setName(String name) {
        this.name = name;
    }
}

public class ConcretePrototype2 implements Prototype {
    private String name;
    public Prototype clone(){
        ConcretePrototype2 prototype = new ConcretePrototype2();
        prototype.setName(this.name);
        return prototype;
    }
    public String toString(){
        return "Now in Prototype2 , name = " + this.name;
    }
    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }
}

原型管理器角色保持一個聚集,作爲對所有原型對象的登記,這個角色提供必要的方法,供外界增加新的原型對象和取得已經登記過的原型對象。

public class PrototypeManager {
    /**
     * 用來記錄原型的編號和原型實例的對應關係
     */
    private static Map<String,Prototype> map = new HashMap<String,Prototype>();
    /**
     * 私有化構造方法,避免外部創建實例
     */
    private PrototypeManager(){}
    /**
     * 向原型管理器裏面添加或是修改某個原型註冊
     * @param prototypeId 原型編號
     * @param prototype    原型實例
     */
    public synchronized static void setPrototype(String prototypeId , Prototype prototype){
        map.put(prototypeId, prototype);
    }
    /**
     * 從原型管理器裏面刪除某個原型註冊
     * @param prototypeId 原型編號
     */
    public synchronized static void removePrototype(String prototypeId){
        map.remove(prototypeId);
    }
    /**
     * 獲取某個原型編號對應的原型實例
     * @param prototypeId    原型編號
     * @return    原型編號對應的原型實例
     * @throws Exception    如果原型編號對應的實例不存在,則拋出異常
     */
    public synchronized static Prototype getPrototype(String prototypeId) throws Exception{
        Prototype prototype = map.get(prototypeId);
        if(prototype == null){
            throw new Exception("您希望獲取的原型還沒有註冊或已被銷燬");
        }
        return prototype;
    }
}

客戶端角色:

public class Client {
    public static void main(String[]args){
        try{
            Prototype p1 = new ConcretePrototype1();
            PrototypeManager.setPrototype("p1", p1);
            //獲取原型來創建對象
            Prototype p3 = PrototypeManager.getPrototype("p1").clone();
            p3.setName("張三");
            System.out.println("第一個實例:" + p3);
            //有人動態的切換了實現
            Prototype p2 = new ConcretePrototype2();
            PrototypeManager.setPrototype("p1", p2);
            //重新獲取原型來創建對象
            Prototype p4 = PrototypeManager.getPrototype("p1").clone();
            p4.setName("李四");
            System.out.println("第二個實例:" + p4);
            //有人註銷了這個原型
            PrototypeManager.removePrototype("p1");
            //再次獲取原型來創建對象
            Prototype p5 = PrototypeManager.getPrototype("p1").clone();
            p5.setName("王五");
            System.out.println("第三個實例:" + p5);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

3、優點和缺點

(1)優點
原型模式允許在運行時動態改變具體的實現類型。原型模式可以在運行期間,由客戶來註冊符合原型接口的實現類型,也可以動態地改變具體的實現類型,看起來接口沒有任何變化,但其實運行的已經是另外一個類實例了。因爲克隆一個原型就類似於實例化一個類。
(2)缺點
原型模式最主要的缺點是每一個類都必須配備一個克隆方法。配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類來說不是很難,而對於已經有的類不一定很容易,特別是當一個類引用不支持序列化的間接對象,或者引用含有循環結構的時候。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章