23種設計模式

整體分爲3大類:
建立型模式 (5種):工廠方法、抽象工廠、單例、建造者、原型
結構型模式(7種):適配器、裝飾器、代理、外觀、橋接、組合、享元
行爲型模式(11種):策略、模板方法、觀察者、迭代子、責任鏈、命令、備忘錄、狀態、訪問者、中介者、解釋器
其它(2種):併發型、線程池java

設計模式的6大原則

  • 開閉原則(總原則):對擴展開放,對修改關閉,須要使用接口和抽象類
  1. 單一職責:每一個類應該實現單一的職責
  2. 里氏替換:基類能夠出現的地方,子類必定能夠出現。相似於向上轉型,子類對父類的方法儘可能不要重寫和重載,會破壞父類定義好的與外界交互規範。是對開閉原則的補充,實現開閉原則的關鍵是抽象化,里氏替換原則是對實現抽象化規範
  3. 依賴倒轉:面向接口編程,依賴於抽象,不依賴於具體。代碼中:不與具體類交互,與接口交互。開閉原則的基礎。
  4. 接口隔離:接口中的方法在子類中必定能用到,不然拆分紅多個接口
  5. 迪米特法則(最少知道):對外暴露越少越好
  6. 合成複用:儘可能使用合成/聚合的方式,而不是使用繼承

建立型模式(5種):工廠方法、抽象工廠、單例、建造者、原型。

1、工廠方法模式

clipboard.png
建立1個接口、2個實現類編程

public interface Sender {
    void send();
}
public class MailSender implements Sender {

    @Override
    public void send() {
        System.out.println("this is mailsender!");
    }
}
public class SmsSender implements Sender {

    @Override
    public void send() {
        System.out.println("this is smssender!");
    }
}

建立1個工廠接口、2個工廠實現類segmentfault

public interface Provider {
    Sender produce();
}
public class SendMailFactory implements Provider {
    @Override
    public Sender produce() {
        return new MailSender();
    }
}
public class SendSmsFactory implements Provider {
    @Override
    public Sender produce() {
        return new SmsSender();
    }
}

測試類設計模式

public class Test {
    public static void main(String[] args) {
        Provider provider = new SendMailFactory();
        Sender sender = provider.produce();
        sender.send();
    }
}

clipboard.png

若是想增長一個功能,則只需作一個實現類,實現 Sender 接口,同時作一個工廠類,實現 Provider 接口,就 OK 了,無需去改動現成的代碼。這樣作,拓展性較好!多線程

2、抽象工廠模式

工廠方法模式和抽象工廠模式的區別以下:
工廠方法模式:併發

  • 1個抽象產品類能夠派生出多個具體產品類。
  • 1個抽象工廠類能夠派生出多個具體工廠類。1個具體工廠類只能建立1個具體產品類的實例。

抽象工廠模式:ide

  • 多個抽象產品類,1個抽象產品類能夠派生出多個具體產品類。
  • 1個抽象工廠類能夠派生出多個具體工廠類。1個具體工廠類能夠建立多個具體產品類的實例,也就是建立的是一個產品線下的多個產品。

對於 java 來講,你能見到的大部分抽象工廠模式都是這樣的:
---它的裏面是一堆工廠方法,每一個工廠方法返回某種類型的對象。
好比說工廠能夠生產鼠標和鍵盤。那麼抽象工廠的實現類(它的某個具體子類)的對象均可以生產鼠標和鍵盤,但可能工廠 A 生產的是羅技的鍵盤和鼠標,工廠 B 是微軟的。
用了工廠方法模式,你替換生成鍵盤的工廠方法,就能夠把鍵盤從羅技換到微軟。可是用了抽象工廠模式,你只要換家工廠,就能夠同時替換鼠標和鍵盤一套。若是你要的產品有幾十個,固然用抽象工廠模式一次替換所有最方便(這個工廠會替你用相應的工廠方法)因此說抽象工廠就像工廠,而工廠方法則像是工廠的一種產品生產線性能

3、單例模式(Singleton)

單例模式能保證在一個JVM中該對象只有一個實例存在。好處:
一、 避免頻繁建立對象,節省系統開銷,減輕 GC 壓力。
二、在系統中某些對象只能有一個(好比一個軍隊出現了多個司令員同時指揮,確定會亂成一團)測試

  1. 簡單的單例類(單線程):
    只能在單線程中用,不能用於多線程。優化

    public class Singleton {
        /* 持有私有靜態實例,防止被引用,此處賦值爲 null,目的是實現延遲加載 */
        private static Singleton instance = null;
    
        /* 私有構造方法,防止被實例化 */
        private Singleton() {
        }
    
        /* 靜態工程方法,建立實例 */
        public static Singleton getInstance() {
            if (instance == null){
                instance = new Singleton();
            }
            return instance;
        }
    
        /* 若是該對象被用於序列化,能夠保證對象在序列化先後保持一致 */
        public Object readResolve(){
            return instance;
        }
    }

    同步代碼塊:

    /* 靜態工程方法,建立實例 */
    public static Singleton getInstance() {
        if (instance == null){
            synchronized (instance){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    在 Java 指令中建立對象和賦值操做是分開進行的,也就是說 instance = new Singleton();語句是分兩步執行的,可能會先爲Singleton實例分配空間,再賦值給instance,最後初始化Singleton實例。
    A、B 兩個線程爲例:

    1. A、B 線程同時進入了第一個 if 判斷
    2. A首先進入 synchronized 塊,因爲 instance 爲 null,因此它執行 instance = new Singleton();
    3. 因爲 JVM 內部的優化機制,JVM 先畫出了一些分配給 Singleton 實例的空白內存,並賦值給 instance 成員(注意此時 JVM 沒有開始初始化這個實例),而後 A 離開了 synchronized塊。
    4. B進入 synchronized 塊,因爲 instance 此時不是 null,所以它立刻離開了 synchronized 塊並將結果返回給調用該方法的程序。
    5. 此時 B 線程打算使用 Singleton 實例,卻發現它沒有被初始化,因而錯誤發生了。
      上面這些話能夠理解爲A線程從同步代碼塊出來後,JVM沒有初始化Singleton實例,B線程調用instance時發現Singleton沒有初始化。
  2. 多線程單例
    使用內部類來維護單例的實現,JVM 內部的機制可以保證當一個類被加載的時候,這個類的加載過程是線程互斥的。這樣當咱們第一次調用 getInstance 的時候,JVM 可以幫咱們保證 instance 只被建立一次,而且會保證把賦值給 instance 的內存初始化完畢,這樣咱們就不用擔憂上面的問題。同時該方法也只會在第一次調用的時候使用互斥機制,這樣就解決了低性能問題。

    public class Singleton {
        /* 私有構造方法,防止被實例化 */
        private Singleton() {
        }    
        /* 此處使用一個內部類來維護單例 */
        private static class SingletonFactory{
            private static Singleton instance = new Singleton();
        }    
        /* 獲取實例 */
        public static Singleton getInstance(){
            return SingletonFactory.instance;
        } 
        /* 若是該對象被用於序列化,能夠保證對象在序列化先後保持一致 */
        public Object readResolve(){
            return getInstance();
        }
    }
  3. 將建立和賦值分開,單獨爲建立類加靜態同步方法
    由於咱們只須要在建立類的時候進行同步,因此只要將建立和getInstance()分開,單獨爲建立加 synchronized 關鍵字,也是能夠的

    public class SingletonTest {
        private static SingletonTest instance = null;
        public SingletonTest() {
        }
        private static synchronized void syncInit(){
            if(instance == null){
                instance = new SingletonTest();
            }
        }
        public static SingletonTest getInstance() {
            if (instance == null){
                syncInit();
            }
            return instance;
        }
    }

    補充:用 採用" 影子實例"的辦法爲單例對象的屬性同步更新

    public class SingletonTest {
        private static SingletonTest instance = null;
        private Vector properties = null;
        public Vector getProperties() {
            return properties;
        }
        public SingletonTest() {
        }
        private static synchronized void syncInit(){
            if(instance == null){
                instance = new SingletonTest();
            }
        }
        public static SingletonTest getInstance() {
            if (instance == null){
                syncInit();
            }
            return instance;
        }
        public void updateProperties(){
            SingletonTest shadow = new SingletonTest();
            properties = shadow.getProperties();
        }
    }

    類和靜態方法與靜態類區別:

    1. 靜態類不能實現接口。(從類的角度說是能夠的,可是那樣就破壞了靜態了。由於接口中不容許有 static 修飾的方法,因此即便實現了也是非靜態的)
    2. 單例能夠被延遲初始化,靜態類通常在第一次加載是初始化。之因此延遲加載,是由於有些類比較龐大,因此延遲加載有助於提高性能。
    3. 單例類能夠被繼承,他的方法能夠被覆寫。可是靜態類內部方法都是 static,沒法被覆寫。

4、建造者模式(Builder)

5、原型模式(Prototype)

將一個對象做爲原型,對其進行復制、克隆後產生一個和原對象相似的新對象
淺複製:將一個對象複製後,基本數據類型的變量都會從新建立,而引用類型,指向的仍是原對象所指向的。
深複製:將一個對象複製後,不管是基本數據類型還有引用類型,都是從新建立的。
一個原型類,只須要實現 Cloneable 接口,覆寫 clone 方法,此處 clone 方法能夠改爲任意的名稱,由於 Cloneable 接口是個空接口,你能夠任意定義實現類的方法名,如 cloneA或者cloneB,由於此處的重點是super.clone()這句話,super.clone()調用的是Object的clone()方法,而在 Object 類中,clone()是 native 的。這裏寫一個深淺複製的例子

public class Prototype implements Cloneable, Serializable {

    private static final long serialVersionUID = 1L;
    private String string;
    private SerializableObject obj;

   /*淺複製*/
    public Object clone() throws CloneNotSupportedException{
        Prototype proto = (Prototype)super.clone();
        return proto;
    }

    /*深複製*/
    public Object deepClone() throws IOException, ClassNotFoundException {
        /* 寫入當前對象的二進制流 */
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        /* 讀出二進制流產生的新對象 */
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();

    }

    public String getString() {
        return string;
    }

    public void setString(String string) {
        this.string = string;
    }

    public SerializableObject getObj() {
        return obj;
    }

    public void setObj(SerializableObject obj) {
        this.obj = obj;
    }
}

class SerializableObject implements Serializable{
    private static final long serialVersionUID = 1L;
}
相關文章
相關標籤/搜索