漫談設(shè)計模式之創(chuàng)建型模式

Christopher Alexander說過:“每一個模式描述了一個在我們周圍不斷重復(fù)發(fā)生的問題煌珊,以及該問題的解決方案的核心区宇。這樣你就能一次又一次地使用該方案而不必做重復(fù)勞動∫阌撸”

一般而言西篓,一個模式有四個基本要素:模式名稱、問題憋活、解決方案岂津、效果。

總體來說悦即,設(shè)計模式分為三大類:創(chuàng)建型模式吮成、結(jié)構(gòu)型模式橱乱、行為型模式。其中創(chuàng)建型模式抽象了實例化過程粱甫,它們幫助一個系統(tǒng)獨立于如何創(chuàng)建泳叠、組合和表示它的那些對象。一個類創(chuàng)建型模式使用繼承改變被實例化的類茶宵,而一個對象創(chuàng)建型模式將實例化委托給另一個對象危纫。創(chuàng)建型模式共五種:抽象工廠模式、建造者模式乌庶、工廠方法模式种蝶、原型模式、單例模式安拟。

一蛤吓、 抽象工廠模式

1.1 目的
抽象工廠模式是提供一個創(chuàng)建一系列相關(guān)或相互依賴對象的接口,而無需指定它們具體的類糠赦。

1.2 適用性
(1) 一個系統(tǒng)要獨立于它的產(chǎn)品的創(chuàng)建会傲、組合和表示時;
(2) 一個系統(tǒng)要由多個產(chǎn)品系列中的一個來配置時拙泽;
(3) 當你強調(diào)一系列相關(guān)的產(chǎn)品對象的設(shè)計以便進行聯(lián)合使用時淌山;
(4) 當你提供一個產(chǎn)品類庫,而只想顯示它們的接口而不是實現(xiàn)時顾瞻。

1.3 結(jié)構(gòu)和實現(xiàn)


抽象工廠模式
public interface Sender {  
    public void Send();  
}  

//兩個實現(xiàn)類:
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 sms sender!");  
    }  
}  

//兩個工廠類:
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 interface Provider {  
    public Sender produce();  
}  

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

1.4 優(yōu)缺點
(1) 分離了具體的類泼疑。如果你現(xiàn)在想增加一個功能:發(fā)及時信息,則只需做一個實現(xiàn)類荷荤,實現(xiàn)Sender接口退渗,同時做一個工廠類,實現(xiàn)Provider接口蕴纳,就OK了会油,無需去改動現(xiàn)成的代碼。這樣做古毛,拓展性較好翻翩。
(2) 使得易于交換產(chǎn)品系列。一個具體工廠類在一個應(yīng)用中僅出現(xiàn)一次稻薇。
(3) 有利于產(chǎn)品的一致性嫂冻。當一個系列中的產(chǎn)品對象被設(shè)計成一起工作時,一個應(yīng)用只能使用同一個系列中的對象塞椎。
(4) 難以支持新種類的產(chǎn)品桨仿。難以擴展抽象工廠以生產(chǎn)新種類的產(chǎn)品。因為抽象工廠方法確定了可以被創(chuàng)建的產(chǎn)品集合案狠,但是支持新種類的產(chǎn)品需要擴展該工廠接口服傍,這涉及抽象工廠類及其所有子類的改變暇昂。

二、 建造者模式

2.1 目的
工廠類模式提供的是創(chuàng)建單個類的模式伴嗡,而建造者模式則是將各種產(chǎn)品集中起來進行管理,用來創(chuàng)建復(fù)合對象从铲,所謂復(fù)合對象就是指某個類具有不同的屬性瘪校,其實建造者模式就是前面抽象工廠模式和最后結(jié)合起來得到的。將一個復(fù)雜對象的構(gòu)建與它的表示分離名段,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示阱扬。

2.2 適用性
(1) 當創(chuàng)建復(fù)雜對象的算法應(yīng)該獨立于該對象的組成部分以及它們的裝配方式時;
(2) 當構(gòu)造過程必須允許被構(gòu)造的對象有不同的表示時伸辟。

2.3 代碼實現(xiàn)

public class Builder {  
    private List<Sender> list = new ArrayList<Sender>();  
    public void produceMailSender(int count){  
        for(int i=0; i<count; i++){  
            list.add(new MailSender());  
        }  
    }  
      
    public void produceSmsSender(int count){  
        for(int i=0; i<count; i++){  
            list.add(new SmsSender());  
        }  
    }  
}  

//測試類:
public class Test {  
    public static void main(String[] args) {  
        Builder builder = new Builder();  
        builder.produceMailSender(10);  
    }  
}  

2.4 優(yōu)點
(1) 它使你可以改變一個產(chǎn)品的內(nèi)部表示麻惶。Builder對象提供給導(dǎo)向器一個構(gòu)造產(chǎn)品的抽象接口。該接口使得生成器可以隱藏這個產(chǎn)品的表示和內(nèi)部結(jié)構(gòu)信夫,同時也隱藏了該產(chǎn)品是如何裝配的窃蹋。
(2) 它將構(gòu)造代碼和表示代碼分開。Builder模式通過封裝一個復(fù)雜對象的創(chuàng)建和表示方式提高了對象的模塊性静稻【唬客戶不需要知道定義產(chǎn)品內(nèi)部結(jié)構(gòu)的類的所有信息。
(3) 它使你可對構(gòu)造過程進行更精細的控制振湾。

三杀迹、 工廠方法模式

3.1 目的
定義一個用于創(chuàng)建對象的接口,讓子類決定實例化哪一個類押搪,工廠方法使一個類的實例化延遲到其子類树酪。

3.2 適用性
(1) 當一個類不知道它所必須創(chuàng)建的對象的類的時候;
(2) 當一個類希望由它的子類來指定它所創(chuàng)建的對象的時候大州。

3.3 結(jié)構(gòu)與實現(xiàn)
(1) 普通工廠模式续语,就是建立一個工廠類,對實現(xiàn)了同一接口的一些類進行實例的創(chuàng)建摧茴。首先看下關(guān)系圖:


普通工廠模式
//共同接口:
public interface Sender {  
    public void Send();  
}  
//創(chuàng)建實現(xiàn)類:
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 sms sender!");  
    }  
}  
//建工廠類:
public class SendFactory {  
    public Sender produce(String type) {  
        if ("mail".equals(type)) {  
            return new MailSender();  
        } else if ("sms".equals(type)) {  
            return new SmsSender();  
        } else {  
            System.out.println("請輸入正確的類型!");  
            return null;  
        }  
    }  
}  
//測試:
public class FactoryTest {  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produce("sms");  
        sender.Send();  
    }  
}  

(2)多個工廠方法模式绵载,是對普通工廠方法模式的改進,在普通工廠方法模式中苛白,如果傳遞的字符串出錯娃豹,則不能正確創(chuàng)建對象,而多個工廠方法模式是提供多個工廠方法购裙,分別創(chuàng)建對象懂版。關(guān)系圖:


多個工廠模式

將上面的代碼做下修改,改動下SendFactory類就行躏率,如下:

public class SendFactory {  
   public Sender produceMail(){  
        return new MailSender();  
    }  
      
    public Sender produceSms(){  
        return new SmsSender();  
    }  
}  
//測試類如下:
public class FactoryTest {  
    public static void main(String[] args) {  
        SendFactory factory = new SendFactory();  
        Sender sender = factory.produceMail();  
        sender.Send();  
    }  
}  

(3)靜態(tài)工廠方法模式躯畴,將上面的多個工廠方法模式里的方法置為靜態(tài)的民鼓,不需要創(chuàng)建實例,直接調(diào)用即可蓬抄。

public class SendFactory {      
    public static Sender produceMail(){  
        return new MailSender();  
    }  
      
    public static Sender produceSms(){  
        return new SmsSender();  
    }  
}  
public class FactoryTest {  
    public static void main(String[] args) {      
        Sender sender = SendFactory.produceMail();  
        sender.Send();  
    }  
}  

大多數(shù)情況下丰嘉,我們會選用第三種——靜態(tài)工廠方法模式。

四嚷缭、 原型模式

4.1 目的
原始模型模式通過給出一個原型對象來指明所要創(chuàng)建的對象的類型饮亏,然后用復(fù)制這個原型對象的方法創(chuàng)建出更多同類型的對象。原始模型模式允許動態(tài)的增加或減少產(chǎn)品類阅爽,產(chǎn)品類不需要非得有任何事先確定的等級結(jié)構(gòu)路幸,原始模型模式適用于任何的等級結(jié)構(gòu)。

4.2 適用性
(1) 當要實例化的類是運行時刻指定時付翁,例如简肴,通過動態(tài)裝載;或者
(2) 為了避免創(chuàng)建一個與產(chǎn)品類層次平行的工廠類層次時百侧;或者
(3) 當一個類的實例只能有幾個不同狀態(tài)組合中的一種時砰识。建立相應(yīng)數(shù)目的原型并克隆它們可能比每次用合適的狀態(tài)手工實例化該類更方便一些。

4.3 代碼實現(xiàn)

//創(chuàng)建一個原型類:
public class Prototype implements Cloneable {  
    public Object clone() throws CloneNotSupportedException {  
        Prototype proto = (Prototype) super.clone();  
        return proto;  
    }  
}  

一個原型類只需要實現(xiàn)Cloneable接口移层,覆寫clone方法仍翰,重點是調(diào)用super.clone(),進而調(diào)用Object的clone()方法观话。結(jié)合對象的淺復(fù)制和深復(fù)制詳細介紹一下:

  • 淺復(fù)制:將一個對象復(fù)制后予借,基本數(shù)據(jù)類型的變量都會重新創(chuàng)建,而引用類型频蛔,指向的還是原對象所指向的灵迫。
  • 深復(fù)制:將一個對象復(fù)制后,不論是基本數(shù)據(jù)類型還有引用類型晦溪,都是重新創(chuàng)建的瀑粥。簡單來說,就是深復(fù)制進行了完全徹底的復(fù)制三圆,而淺復(fù)制不徹底狞换。要實現(xiàn)深復(fù)制,需要采用流的形式讀入當前對象的二進制輸入舟肉,再寫出二進制數(shù)據(jù)對應(yīng)的對象修噪。
//深淺復(fù)制的例子:
public class Prototype implements Cloneable, Serializable {  
    private static final long serialVersionUID = 1L;  
    private String string;  
    private SerializableObject obj;  
  
    /* 淺復(fù)制 */  
    public Object clone() throws CloneNotSupportedException {  
        Prototype proto = (Prototype) super.clone();  
        return proto;  
    }  
  
    /* 深復(fù)制 */  
    public Object deepClone() throws IOException, ClassNotFoundException {  
        /* 寫入當前對象的二進制流 */  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(this);  
  
        /* 讀出二進制流產(chǎn)生的新對象 */  
        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;  
}  

4.4 優(yōu)點
(1) 運行時刻增加和刪除產(chǎn)品。原型模式允許只通過客戶注冊原型實例就可以將一個新的具體產(chǎn)品類并入系統(tǒng)路媚。
(2) 改變值以指定新對象黄琼。高度動態(tài)的系統(tǒng)允許你通過對象復(fù)合定義新的行為。
(3) 改變構(gòu)造以指定新對象整慎。許多應(yīng)用由部件和子部件來創(chuàng)建對象脏款。
(4) 減少子類的構(gòu)造围苫。
(5) 用類動態(tài)配置應(yīng)用。

五撤师、 單例模式

5.1 目的
保證一個類僅有一個實例剂府,并提供一個訪問它的全局訪問點。單例對象能保證在一個JVM中剃盾,該對象只有一個實例存在周循。單例模式具有以下幾個特點:

1、單例類有且僅有一個實例万俗。  
2饮怯、單例類必須自己創(chuàng)建自己的唯一實例闰歪。  
3蓖墅、單例類必須給所有其他對象提供單一實例库倘。

5.2 適用性
(1) 對唯一實例的受控訪問。單例類封裝它的唯一實例论矾,所以它可以嚴格的控制客戶怎么以及何時訪問它教翩。
(2) 縮小名空間。避免了那些存儲唯一實例的全局變量污染名空間贪壳。
(3) 允許對操作和表示的精華饱亿。單例類可以有子類,而且用這個擴展類的實例來配置一個應(yīng)用是很容易的闰靴。
(4) 允許可變數(shù)目的實例彪笼。
(5) 比類操作更靈活。

5.3 實現(xiàn)
單例分為兩種:懶漢式單例蚂且、餓漢式單例配猫。
5.3.1 懶漢單例
首先介紹下什么是延遲加載,延遲加載是程序真正需要使用的時才去創(chuàng)建實例杏死,不用時不創(chuàng)建實例泵肄。依據(jù)延遲加載可以把懶漢單例分為以下幾類。

(1) 同步延遲加載單例

public class Singleton {  
    /* 持有私有靜態(tài)實例淑翼,防止被引用腐巢,此處賦值為null,目的是實現(xiàn)延遲加載 */  
    private static Singleton instance = null;  
  
    /* 私有構(gòu)造方法窒舟,防止被實例化 */  
    private Singleton() {  
    }  
  
    /* 靜態(tài)工程方法系忙,創(chuàng)建實例 */  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

(2) 雙重檢測同步延遲加載單例

public class Singleton {  
  private volatile static Singleton instance = null;  
  private Singleton() {}  

  public static Singleton getInstance() {  
    if (instance == null) {  // 1
        synchronized (Singleton.class) {
         if (instance == null) {// 2
             instance = new Singleton();  // 3
          }  
       }  
    }  
    return instance;  
   }  
} 

雙重檢測同步延遲加載單例線程并非絕對安全;不能防止反序列化惠豺、反射產(chǎn)生新的實例银还。

線程不安全原因是Java的亂序執(zhí)行风宁、初始化對象需要時間。
對于語句3蛹疯,JVM在執(zhí)行時大致做了下述三件事:
a. 在內(nèi)存中分配一塊內(nèi)存戒财;
b. 調(diào)用構(gòu)造方法;
c. 將內(nèi)存的地址指向instance變量捺弦。(執(zhí)行這一步后饮寞,instance != null)

如果按照abc的順序執(zhí)行也不會有什么問題。但由于Java亂序執(zhí)行的機制列吼,有可能在真實情況下執(zhí)行順序為acb幽崩。

假設(shè)t1、t2是兩個線程寞钥。t1執(zhí)行到1時慌申,發(fā)現(xiàn)為null,于是執(zhí)行到語句3理郑,先執(zhí)行a,再執(zhí)行c,在還沒有執(zhí)行b時蹄溉,時間片給了t2。這時您炉,由于instance已經(jīng)分配了地址空間柒爵,instance不為null了。所以t2在執(zhí)行到語句1后直接return instance赚爵,獲得了這個還沒有被初始化的對象棉胀,然后在使用時就報錯了。因為有可能得到了實例的正確引用冀膝,但卻訪問到其成員變量的不正確值膏蚓。

(3) 內(nèi)部類實現(xiàn)延遲加載單例

public class Singleton {  
   private Singleton() {}  
   public static class Holder {  
    // 這里的私有沒有什么意義  
    /* private */static Singleton instance = new Singleton();  
   }  
   public static Singleton getInstance() {  
    // 外圍類能直接訪問內(nèi)部類(不管是否是靜態(tài)的)的私有變量  
    return Holder.instance;  
   }  
}

這種比上面1、2都好一些畸写,既實現(xiàn)了線程安全驮瞧,又避免了同步帶來的性能影響。

5.3.2 餓漢模式
(1) 非延遲加載單例枯芬,也稱為餓漢模式

public class Singleton {  
    private Singleton() {}  
    private static final Singleton instance = new Singleton();  

    public static Singleton getInstance() {  
         return instance;  
    }  
}

餓漢模式在類創(chuàng)建的同時就已經(jīng)創(chuàng)建好一個靜態(tài)的對象供系統(tǒng)使用论笔,以后不再改變,所以天生是線程安全的千所。但是如果類創(chuàng)建時失敗狂魔,則永遠無法獲得該類的實例化。

5.4 優(yōu)點
(1)減少了類的頻繁創(chuàng)建淫痰,特別是減少大型對象的創(chuàng)建最楷,很大程度上節(jié)約了系統(tǒng)開銷。
(2)減少使用new操作符,降低了系統(tǒng)內(nèi)存的使用頻率籽孙,減輕GC壓力烈评。
(3)保證系統(tǒng)核心服務(wù)獨立控制整個系統(tǒng)操作流程,如果有多個實例的話犯建,系統(tǒng)將出現(xiàn)混亂讲冠。如同一個軍隊出現(xiàn)了多個司令員同時指揮,肯定會亂成一團适瓦,所以系統(tǒng)的核心控制模塊必須使用單例模式竿开,才能保證系統(tǒng)正常運行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玻熙,一起剝皮案震驚了整個濱河市否彩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗦随,老刑警劉巖胳搞,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異称杨,居然都是意外死亡,警方通過查閱死者的電腦和手機筷转,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門姑原,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呜舒,你說我怎么就攤上這事锭汛。” “怎么了袭蝗?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵唤殴,是天一觀的道長。 經(jīng)常有香客問我到腥,道長朵逝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任乡范,我火速辦了婚禮配名,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晋辆。我一直安慰自己渠脉,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布瓶佳。 她就那樣靜靜地躺著芋膘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上为朋,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天臂拓,我揣著相機與錄音,去河邊找鬼潜腻。 笑死埃儿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的融涣。 我是一名探鬼主播童番,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼威鹿!你這毒婦竟也來了剃斧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤忽你,失蹤者是張志新(化名)和其女友劉穎幼东,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體科雳,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡根蟹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了糟秘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片简逮。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尿赚,靈堂內(nèi)的尸體忽然破棺而出散庶,到底是詐尸還是另有隱情,我是刑警寧澤凌净,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布悲龟,位于F島的核電站,受9級特大地震影響冰寻,放射性物質(zhì)發(fā)生泄漏须教。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一斩芭、第九天 我趴在偏房一處隱蔽的房頂上張望没卸。 院中可真熱鬧,春花似錦秒旋、人聲如沸约计。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煤蚌。三九已至耕挨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尉桩,已是汗流浹背筒占。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工但骨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掌挚,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓谨履,卻偏偏與公主長得像这橙,于是被迫代替她去往敵國和親奏窑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容