《設計模式》2.建立型模式


點擊進入個人博客

2.1 簡單工廠模式

2.1.1 工廠模式的幾種形態

工廠模式主要用一下幾種形態:java

  1. 簡單工廠(Simple Factory):專門定義一個類來負責建立其餘類的實例,被建立的實例一般都具備共同的父類。它又稱爲靜態工廠方法模式。
  2. 工廠方法(Factory Method):提早定義用於建立對象的接口,讓子類決定實例化具體的某一個類,即在工廠和產品中間增長接口,工廠再也不負責產品的建立,由接口針對不一樣條件返回具體的類實例,由具體類實例去實現。又稱爲多態性工廠模式或虛擬構造子模式。
  3. 抽象工廠(Abstract Factory):抽象工廠模式是指當有多個抽象角色時,使用的一種工廠模式。抽象工廠模式能夠向客戶端提供一個接口,使客戶端在沒必要指定產品的具體的狀況下,建立多個產品族中的產品對象。又稱爲工具箱模式。

2.1.2 簡單工廠模式

簡單工廠模式

簡單工廠模式
  • 簡單工廠模式(Simple Factory Pattern)又稱爲靜態工廠方法(Static Factory Method)模式,它屬於類建立型模式。
  • 在簡單工廠模式中,能夠根據自變量的不一樣返回不一樣類的實例。
  • 簡單工廠模式專門定義一個類來負責建立其餘類的實例,被建立的實例一般都具備共同的父類。
簡單工廠模式的三個角色
  1. 工廠類(Creator)角色:擔任這個角色的是工廠方法模式的核心,含有與應用緊密相關的商業邏輯。工廠類在客戶端的直接調用下建立產品對象,它每每由一個具體Java類實現。
  2. 抽象產品(Product)角色:擔任這個角色的類是工廠方法模式所建立的對象的父類,或它們共同擁有的接口。抽象產品角色能夠用一個Java接口或者Java抽象類實現。
  3. 具體產品(Concrete Product)角色:工廠方法模式所建立的任何對象都是這個角色的實例,具體產品角色由一個具體Java 類實現。
abstract class Fruit {}
class Apple extends Fruit {}
class Banana extends Fruit {}

class FruitFactory {
    public static Fruit newInstance(Class<? extends Fruit> clz)  {
        try {
            return clz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
多個工廠方法

每一個工廠類能夠有多於一個的工廠方法,分別負責建立不一樣的產品對象。好比java.text.DateFormat類是其子類的工廠類,而它就提供了多個靜態工廠方法。算法

工廠角色與抽象產品角色合併

在有些狀況下,工廠角色能夠由抽象產品角色扮演。典型的應用就是java.text.DateFormat類,一個抽象產品類同時是子類的工廠。數據庫

三個角色所有合併

若是抽象產品角色已經被忽略,而工廠角色就能夠與具體產品角色合併。換言之,一個產品類爲自身的工廠。編程

class ConcreteProduct {
    public static ConcreteProduct factory() {
        return new ConcreteProduct();
    }
}

2.1.3 簡單工廠模式與其餘模式的關係

單例模式
  • 單例模式使用了簡單工廠模式。換言之,單例類具備一個靜態工廠方法提供自身的實例。
  • 單例模式並非簡單工廠模式的退化情形,單例模式要求單例類的構造方法是私有的,從而客戶端不能直接將之實例化,而必須經過這個靜態工廠方法將之實例化
  • 單例類自身是本身的工廠角色。換言之,單例類本身負責建立自身的實例。
  • 單例類使用一個靜態的屬性存儲本身的唯一實例 ,工廠方法永遠僅提供這一個實例
多例模式
  • 多例模式是對單例模式的推廣。多例模式與單例模式的共同之處在於它們都禁止外界直接將之實例化,同時經過靜態工廠方法向外界提供循環使用的自身的實例。它們的不一樣在於單例模式僅有一個實例,而多例模式則能夠有多個實例
  • 多例模式每每具備一個彙集屬性,經過向這個彙集屬性登記已經建立過的實例達到循環使用實例的目的。通常而言,一個典型的多例類具備某種內部狀態,這個內部狀態能夠用來區分各個實例;而對應於每個內部狀態,都只有一個實例存在。
  • 根據外界傳入的參量,工廠方法能夠查詢本身的登記彙集,若是具備這個狀態的實例已經存在,就直接將這個實例提供給外界;反之,就首先建立一個新的知足要求的實例,將之登記到彙集中,而後再提供給客戶端。
備忘錄模式
  • 單例和多例模式使用了一個屬性或者彙集屬性來登記所建立的產品對象, 以即可以經過查詢這個屬性或者彙集屬性找到和共享已經建立了的產品對象。這就是備忘錄模式的應用。
MVC模式

MVC

  • 簡單工廠模式所建立的對象每每屬於一個產品等級結構,這個等級結構能夠是MVC模式中的視圖(View);而工廠角色自己能夠是控制器(Controller)。一個MVC 模式能夠有一個控制器和多個視圖,如上圖所示。
  • 上圖中的Controller(控制器)也就是工廠角色,它負責建立產品View(視圖)。
  • 若是系統須要有多個控制器參與這個過程的話,簡單工廠模式就不適用了,應當考慮使用工廠方法模式。

2.1.4 簡單工廠模式的優勢和缺點

簡單工廠模式的優勢
  1. 工廠類含有必要的判斷邏輯,能夠決定在何時建立哪個產品類的實例,客戶端能夠免除直接建立產品對象的責任,而僅僅「消費」產品;簡單工廠模式經過這種作法實現了對責任的分割,它提供了專門的工廠類用於建立對象。
  2. 客戶端無需知道所建立的具體產品類的類名,只須要知道具體產品類所對應的參數便可,對於一些複雜的類名,經過簡單工廠模式能夠減小使用者的記憶量。
  3. 經過引入配置文件,能夠在不修改任何客戶端代碼的狀況下更換和增長新的具體產品類,在必定程度上提升了系統的靈活性。
簡單工廠模式的缺點
  1. 因爲工廠類集中了全部產品建立邏輯,一旦不能正常工做,整個系統都要受到影響。
  2. 使用簡單工廠模式將會增長系統中類的個數,在必定程序上增長了系統的複雜度和理解難度。
  3. 系統擴展困難,一旦添加新產品就不得不修改工廠邏輯,在產品類型較多時,有可能形成工廠邏輯過於複雜,不利於系統的擴展和維護。
  4. 簡單工廠模式因爲使用了靜態工廠方法,形成工廠角色沒法造成基於繼承的等級結構。
簡單工廠模式適用環境
  1. 工廠類負責建立的對象比較少,因爲建立的對象較少,不會形成工廠方法中的業務邏輯太過複雜。
  2. 客戶端只知道傳入工廠類的參數,對於如何建立對象不關心;客戶端既不須要關心建立細節,甚至連類名都不須要記住,只須要知道類型所對應的參數。

2.2 工廠方法模式

2.2.1 工廠方法模式簡介

  • 工廠方法模式是類的建立模式,又叫作虛擬構造子模式或多態性工廠模式。
  • 在工廠方法模式中,核心的工廠類再也不負責全部的產品的建立,而是將具體建立的工做交給子類去作。該核心類成爲一個抽象工廠角色,僅負責給出具體工廠子類必須實現的接口,而不接觸哪個產品類應當被實例化這種細節。

2.2.2 工廠方法的結構

工廠方法模式

  • 抽象工廠(Creator)角色:擔任這個角色的是工廠方法模式的核心,它是與應用程序無關的。任何在模式中建立對象的工廠類必須實現這個接口。在實際的系統中,這個角色也經常使用抽象類實現。
  • 具體工廠(Concrete Creator)角色:擔任這個角色的是實現了抽象工廠接口的具體JAVA類。具體工廠角色含有與業務密切相關的邏輯,而且受到應用程序的調用以建立導出類。
  • 抽象產品(Product)角色:工廠方法模式所建立的對象的超類,也就是全部產品對象的共同父類或共同擁有的接口。在實際的系統中,這個角色也經常使用抽象類實現。
  • 具體產品(Concrete Product)角色:這個角色實現了抽象產品(Product)角色所聲明的接口,工廠方法模式所建立的每個對象都是某個具體產品角色的實例。
abstract class Fruit {}
abstract class FruitFactory {
    public abstract Fruit newInstance();
}

class Apple extends Fruit {}
class Banana extends Fruit {}

class AppleFactory extends FruitFactory {
    @Override
    public Fruit newInstance() {
        return new Apple();
    }
}

class BananaFactory extends FruitFactory {
    @Override
    public Fruit newInstance() {
        return new Banana();
    }
}

2.2.3 工廠方法模式的細節

Java中的應用

java.util.Collection接口繼承來Iterable接口,全部其子類都必須實現Iterator<T> iterator()方法,這個iterator()方法就是一個工廠方法。緩存

使用接口或者抽象類

抽象工廠角色和抽象產品角色均可以選擇由Java接口或者Java抽象類來實現。
若是具體工廠角色由共同的邏輯,那麼這些共同的邏輯就能夠向上移動到抽象工廠角色中,這也意味着抽象工廠角色應該由抽象類實現;反之就應當由接口實現。網絡

使用多個工廠方法

抽象工廠角色能夠規定出多於一個的工廠方法,從而使具體工廠角色實現這些不一樣的工廠方法。併發

工廠方法模式的優勢
  1. 隱藏細節:在工廠方法模式中,工廠方法用來建立客戶所須要的產品,同時還向客戶隱藏了哪一種具體產品類將被實例化這一細節,用戶只須要關心所需產品對應的工廠,無須關心建立細節,甚至無須知道具體產品類的類名。
  2. 多態性設計:工廠方法模式之因此又被稱爲多態工廠模式,是由於全部的具體工廠類都具備同一抽象父類。基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵,它可以使工廠能夠自主肯定建立何種產品對象,而如何建立這個對象的細節則徹底封裝在具體工廠內部
  3. 徹底符合開閉原則:在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其餘的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就能夠了。
工廠方法模式的缺點
  1. 類數量太多:在添加新產品時,須要編寫新的具體產品類,並且還要提供與之對應的具體工廠類,系統中類的個數將成對增長,在必定程度上增長了系統的複雜度,有更多的類須要編譯和運行,會給系統帶來一些額外的開銷。
  2. 系統的抽象性和複雜性:因爲考慮到系統的可擴展性,須要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增長了系統的抽象性和理解難度,且在實現時可能須要用到DOM、反射等技術,增長了系統的實現難度。
模式適用環境
  1. 一個類不知道它所須要的對象的類:在工廠方法模式中,客戶端不須要知道具體產品類的類名,只須要知道所對應的工廠便可,具體的產品對象由具體工廠類建立;客戶端須要知道建立具體產品的工廠類。
  2. 一個類經過其子類來指定建立哪一個對象:在工廠方法模式中,對於抽象工廠類只須要提供一個建立產品的接口,而由其子類來肯定具體要建立的對象,利用面向對象的多態性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。
  3. 動態指定:將建立對象的任務委託給多個工廠子類中的某一個,客戶端在使用時能夠無須關心是哪個工廠子類建立產品子類,須要時再動態指定,可將具體工廠類的類名存儲在配置文件或數據庫中

2.2.4 工廠方法模式與其餘模式

簡單工廠模式
  1. 工廠方法模式和簡單工廠模式在結構上的不一樣很明顯。工廠方法模式的核心是一個抽象工廠類,而簡單工廠模式把核心放在一個具體類上
  2. 若是系統須要加入一個新的導出類型,那麼所須要的就是向系統中加入一個這個導出類以及所對應的工廠類。沒有必要修改客戶端,也沒有必要修改抽象工廠角色或者其餘已有的具體工廠角色。對於增長新的導出類型而言,這個系統徹底支持「開-閉原則」。
模板方法模式

工廠方法模式經常與模版方法模式一塊兒聯合使用。緣由其實不難理解:第一,兩個模式都是基於方法的,工廠方法模式是基於多態性的工廠方法的,而模版方法模式是基於模版方法和基本方法的;第二,兩個模式都將具體工做交給子類。工廠方法模式將建立工做推延給子類,模版方法模式將剩餘邏輯交給子類。app

MVC模式

MVC
工廠方法模式老是涉及到兩個等級結構中的對象,而這兩個等級結構能夠分別是MVC中的控制器和試圖。一個MVC模式能夠有多個控制器和多個視圖。
若是系統內只須要一個控制器,那麼能夠簡化爲簡單工廠模式。ide

享元模式

享元模式使用了帶有循環邏輯的工廠方法。函數

2.3 抽象工廠模式

2.3.1 抽象工廠模式簡介

  • 抽象工廠模式是全部形態的工廠模式中最爲抽象和具備通常性的形態。
  • 「抽象」來自「抽象產品角色」,「抽象工廠」就是抽象產品角色的工廠。
  • 抽象工廠模式與工廠方法模式最大的區別在於,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則須要面對多個產品等級結構

2.3.2 抽象工廠方式結構

抽象工廠方式

  • 抽象工廠(Creator)角色:擔任這個角色的是抽象方法模式的核心,它是與應用程序無關的。
  • 具體工廠(Concrete Creator)角色:具體工廠角色含有與業務密切相關的邏輯,而且受到應用程序的調用以建立導出類。
  • 抽象產品(Product)角色:抽象方法模式所建立的對象的超類,也就是全部產品對象的共同父類或共同擁有的接口。
  • 具體產品(Concrete Product)角色:抽象工廠模式所建立的每個對象都是某個具體產品角色的實例。

2.3.3 抽象工廠方式細節

抽象方法模式場景
  1. 一個系統不該當依賴於產品類實例如何被建立、組合和表達的細節。這對於全部形態的工廠模式都是重要的;
  2. 一個系統的產品有多於一個的產品族,而系統只消費其中某一族的產品;
  3. 同屬於同一個產品族的產品是在一塊兒使用的,這一約束必需要在系統的設計中體現出來;
  4. 系統提供一個產品類的庫,全部的產品以一樣的接口出現,從而使客戶端不依賴於實現。
抽象方法模式優勢
  1. 隔離了具體類的生成,使得用戶不須要知道什麼被建立了。
  2. 當一個產品族中的多個對象被設計成一塊兒工做時,它可以保證客戶端始終只使用同一個產品族中的對象。
抽象方法模式缺點
  • 抽象工廠的接口肯定了能夠被建立的產品集合,因此難以擴展抽象工廠以生成新種類的產品。

2.3.4 三種工廠模式總結

下面例子中,手機、電腦是抽象產品,蘋果、三星等是工廠。

簡單工廠模式
  • 抽象產品叫手機
  • 具體產品是蘋果手機、三星手機
  • 工廠有一個生產手機的方法,能夠根據傳入品牌是蘋果仍是三星決定生產哪一個品牌的手機
工廠方法模式
  • 抽象產品叫手機
  • 具體產品是蘋果手機、三星手機
  • 抽象工廠叫手機工廠
  • 具體工廠是蘋果手機工廠和三星手機工廠,分別生產蘋果手機和三星手機
抽象工廠模式
  • 抽象產品叫手機、電腦
  • 具體產品是蘋果手機、蘋果電腦、三星手機、三星電腦
  • 抽象工廠叫手機電腦工廠,有兩個方法分別是生產手機和生產電腦
  • 具體工廠是蘋果工廠和三星工廠,蘋果工廠的兩個方法分別生產蘋果手機和蘋果電腦,三星工廠的兩個方法分別生產三星手機和三星電腦

2.4 單例模式

單例模式確保某個類只有一個實例,並且自行實例化並向整個系統提供這個實例。

2.4.1 單例模式細節

核心代碼

私有化構造方法!

解決什麼問題
  • 保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
  • 主要解決一個全局使用的類頻繁地建立與銷燬的問題。
單例模式的應用
  1. 屬性文件
  2. Java.lang.Runtime對象
單例模式優勢
  1. 在內存中只有一個實例,減小內存開支,特別是一個對象須要頻繁地建立銷燬時。
  2. 單例模式能夠避免對資源的多重佔用,例如一個寫文件操做,因爲只有一個實例存在內存中,避免對同一個資源文件的同時寫操做。
  3. 單例模式能夠在系統設置全局的訪問點,優化和共享資源訪問,例如,能夠設計一個單例類,負責全部數據表的映射處理。
單例模式缺點
  1. 因爲私有化了構造方法,因此不能繼承
  2. 與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來實例化。
  3. 特別要注意單例對象若是持有Context,那麼容易引起內存泄漏,此時須要注意傳遞給單例對象的context,最好是Application Context

2.4.2 餓漢式與懶漢式

餓漢式
  • 加載類的時候比較慢
  • 運行時得到對象的速度比較快
  • 它從加載到應用結束會一直佔用資源。
class EagerSingleton {
    // 建立單例類對象
    private static EagerSingleton instance = new EagerSingleton();
    // 構造方法私有化
    private EagerSingleton() {}
    // 獲取可用對象
    public static EagerSingleton getInstance() {
        return instance;
    }
}
懶漢式
  • 是運行時得到對象的速度比較慢
  • 加載類的時候比較快
  • 它在整個應用的生命週期只有一部分時間在佔用資源。
class LazySingleton {
    // 聲明單例類對象
    private static LazySingleton instance;
    // 構造方法私有化
    private LazySingleton() {}
    // 獲取可用對象
    public static synchronized LazySingleton getInstance() {
        if(null == instance) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

2.4.3 懶漢式與雙重檢查成例

  1. 因爲2.4.2懶漢式代碼中,直接對整個getInstance()方法進行了同步處理,可能會致使一些性能問題,因而有了下面的改進方法,經過雙重檢查和同步代碼塊的形式來處理懶漢式的併發問題。但要注意的是,這在Java語言中多是問題的,之因此是可能有問題,是由於不一樣Java版本的內存模型不一樣。
  2. 在第一次檢查時,可能會有多個線程同時到達(1)處。假設線程1線程2都到達(1)進行第一次檢查,此時instancenull,兩個線程都經過第一次檢查
  3. 而後因爲同步代碼塊加鎖,只能有一個線程獲取鎖。線程1獲取鎖並向下繼續執行,此時instance仍然爲null,因而執行(5)初始化instance = new Singleton(),而後線程1執行完畢釋放鎖。
  4. 而後線程2獲取鎖,此時第二次檢查判斷instance不爲null,因此線程2不會進行初始化,直接退出,返回已經初始化好的instance
  5. 以上步驟聽起來是沒有問題的,但問題出在instance = new Singleton()這一句話並非原子操做!
class Singleton {
    private static Singleton instance;
    private Singleton() {}

    public static Singleton getInstance() throws Exception {
        if(null == instance) { // (1)第一次檢查
            // (2)這裏會有多個線程同時到達
            synchronized(Singleton.class) { // 同步代碼塊加鎖
                // (3)此處只能是單線程
                if (null == instance) { // (4)第二次檢查
                    instance = new Singleton(); // (5)初始化instance
                }
            }
        }
        return instance;
    }
}
問題出現的緣由:無序寫入

爲展現問題出現的緣由,假設代碼行instance =new Singleton();執行了下列僞代碼:

mem = allocate();             // (1)爲單例對象分配內存空間.
instance = mem;               // (2)注意,instance引用如今已經不是null,但還未初始化
ctorSingleton(instance);      // (3)爲單例對象經過instance調用構造函數

上述僞代碼中,執行的順序多是(1)(3)(2),此時不會致使上述問題;但若是(1)(2)(3)的執行過程,則可能在線程1執行到(2)時,CPU開始執行線程2,此時剛好線程2執行到第一次檢查,獲取到的是一個不爲null但還沒有初始化的值,此時程序會拋出錯誤。

使用volatile

在高版本的JDK中,使用volatile關鍵字能夠保證不會產生上述問題。被volatile所修飾的變量的值不會被本地線程緩存,全部對該變量的讀寫都是直接操做共享內存來實現,從而確保多個線程能正確的處理該變量。
該關鍵字可能會屏蔽掉虛擬機中的一些代碼優化,因此其運行效率可能不是很高。

class Singleton {
    private static volatile Singleton instance;
}
使用內部類
class Singleton {
    private Singleton() {}
    private static class Holder {
        static Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
        // 外圍類能直接訪問內部類(無論是不是靜態的)的私有變量  
        return Holder.instance;
    }
}
更多資料
  1. 單例模式與雙重檢測
  2. 雙重檢查的缺陷
  3. 用happen-before規則從新審視DCL

2.5 多例模式

多例模式實際上就是單例模式的推廣,多例類能夠有多個實例,多例類必須本身建立、管理本身的實例,並向外界提供本身的實例。
多例模式
多例模式分爲有上限多例類和無上限多例類,無上限多例類要經過集合來實現。

2.6 建造者模式

建造者模式(Builder Pattern)使用多個簡單的對象一步一步構建成一個複雜的對象。它提供了一種建立對象的最佳方式。

2.6.1 建造者結構

建造者模式

  • Builder(抽象建造者):能夠是一個抽象類或一個接口,規範產品對象的各個組成部分的建造。
  • ConcreteBuilder(具體建造者):它實現了Builder接口,給出一步一步的建立產品實例的操做,而後提供一個方法返回建立好的複雜產品對象。
  • Product(產品角色):若是是單個產品類,那麼就是一個具體的產品;若是是多個產品類,那麼就是一個抽象的類或接口。
  • ConcreteProduct(具體產品):當多個產品類時,繼承抽象Product,也就是具體的要建造的複雜對象。值得注意的是,這些產品類不必定會有共同的接口。
  • Director(指揮者):它複雜安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關係,能夠在Director的方法中調用建造者對象的部件構造與裝配方法,完成建造複雜對象的任務。

2.6.2 建造者模式細節

主要目的

一個產品一般有不一樣的組成成分做爲產品的零件,不一樣的產品能夠有不一樣的零件,建造產品的過程是建造零件的過程。建造者模式將產品的結構和產品的零件建造過程對外隱藏起來,把對建造過程進行指揮的責任和具體建造零件的責任分割開來,達到責任劃分和封裝的目的。

解決問題

主要解決在開發過程當中,有時須要建立一個複雜對象,一般由多個部分的子對象構成;因爲複雜對象的多樣性,這個複雜對象的各個部分常常面臨着劇烈的變化,可是將它們組合在一塊兒的算法須要保持穩定。

使用場景
  1. 肯德基的產品不少,須要組成「套餐」。
  2. Java的StringBuilder
省略角色
  1. 省略抽象建造者:若是隻須要一個具體建造者,則能夠省略抽象建造者。
  2. 省略指揮者:能夠在具體建造者裏邊直接構造具體產品。
  3. 合併具體建造者和具體產品:在產品自己就是本身的建造者。
優勢
  1. 良好的封裝性
  2. 具體建造類之間獨立,擴展性好
缺點
  • 若是產品比較多,可能會有不少的建造類。

2.6.3 肯德基套餐案例

public class Waiter {
    public static void main(String[] args) {
        KFCBuilder builder = new MexicanTwisterBuilder();
        builder.buildBeverage();
        builder.buildHamburger();
        builder.buildSnack();
        KFCCombo combo = builder.getCombo();
    }
}

// 套餐接口
abstract class KFCCombo {
    private String hamburger;
    private String beverage;
    private String snack;
    // getters & setters
}
// 墨西哥雞肉卷套餐
class MexicanTwisterCombo extends KFCCombo {}

// Builder接口
interface KFCBuilder {
    void buildHamburger();
    void buildBeverage();
    void buildSnack();
    KFCCombo getCombo();
}

class MexicanTwisterBuilder implements KFCBuilder {
    private KFCCombo combo = new MexicanTwisterCombo();
    @Override
    public void buildHamburger() {
        combo.setHamburger("Mexican Twister");
    }

    @Override
    public void buildBeverage() {
        combo.setBeverage("Pepsi Cola");
    }

    @Override
    public void buildSnack() {
        combo.setSnack("Hot Wings");
    }

    @Override
    public KFCCombo getCombo() {
        return combo;
    }
}

2.6.4 builder內部類

若是一個類有不少屬性,此時爲此類寫一個Builder內部類,來輔助建造該類。

class Phone {
    private String screen;
    private String camera;
    private String cpu;
    private String battery;

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private Phone phone = new Phone();

        public Builder screen(String screen) {
            phone.screen = screen;
            return this;
        }

        public Builder camera(String camera) {
            phone.camera = camera;
            return this;
        }

        public Builder cpu(String cpu) {
            phone.cpu = cpu;
            return this;
        }

        public Builder battery(String battery) {
            phone.battery = battery;
            return this;
        }

        public Phone build() {
            return phone;
        }
    }
}

2.6.5 與其餘模式的關係

抽象工廠模式
  • 抽象工廠模式實現對產品家族的建立,一個產品家族是這樣的一系列產品:具備不一樣分類維度的產品組合,採用抽象工廠模式則是不須要關心構建過程,只關心什麼產品由什麼工廠生產便可。
  • 而建造者模式則是要求按照規定建造產品,它的主要目的是經過組裝零配件而產生一個新產品。
  • 換言之,抽象工廠模式在更加具體的維度上,而建造模式在一個更加宏觀的維度上。
策略模式
  • 事實上建造模式是策略模式的一種特殊狀況,這兩種模式的卻別在於用意不一樣。
  • 建造模式適應於爲客戶端一點一點地建造新的對象。
  • 策略模式的目的是爲算法提供抽象的接口。

2.7 原始模型模式

原始模型模式經過給一個原型對象來指明所要建立的對象的類型,而後用複製這個原型對象的辦法建立出更多同類型的對象。

2.7.1 原型模式結構

原型模式
這種模式涉及到三個角色:

  1. 客戶(Client)角色:客戶類提出建立對象的請求
  2. 抽象原型(Prototype)角色:這是一個抽象角色,此角色給出因此的具體原型類所需的接口。
  3. 具體原型(Concrete Prototype):被複制的對象。

2.7.2 原型模式細節

主要目的

用原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象。

適用環境
  1. 建立新對象成本較大(例如初始化時間長,佔用CPU多或佔太多網絡資源),新對象能夠經過複製已有對象來得到,若是類似對象,則能夠對其成員變量稍做修改。
  2. 系統要保存對象的狀態,而對象的狀態很小。
  3. 須要避免使用分層次的工廠類來建立分層次的對象,而且類的實例對象只有一個或不多的組合狀態,經過複製原型對象獲得新實例能夠比使用構造函數建立一個新實例更加方便。
優勢
  1. 當建立對象的實例較爲複雜的時候,使用原型模式能夠簡化對象的建立過程,經過複製一個已有的實例能夠提升實例的建立效率。
  2. 擴展性好,因爲原型模式提供了抽象原型類,在客戶端針對抽象原型類進行編程,而將具體原型類寫到配置文件中,增減或減小產品對原有系統都沒有影響。
  3. 原型模式提供了簡化的建立結構,工廠方法模式經常須要有一個與產品類等級結構相同的工廠等級結構,而原型模式不須要這樣,圓形模式中產品的複製是經過封裝在類中的克隆方法實現的,無需專門的工廠類來建立產品。
  4. 可使用深克隆方式保存對象的狀態,使用原型模式將對象複製一份並將其狀態保存起來,以便在須要的時候使用(例如恢復到歷史某一狀態),可輔助實現撤銷操做。
缺點
  1. 須要爲每個類配置一個克隆方法,並且該克隆方法位於類的內部,當對已有類進行改造的時候,須要修改代碼,違反了開閉原則。
  2. 在實現深克隆時須要編寫較爲複雜的代碼,並且當對象之間存在多重簽到引用時,爲了實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來會比較麻煩。

2.7.3 Java的Clone

Object.clone()

clone()方法返回的對象叫作原始對象的克隆體。一個克隆對象的基本特性必須是:

  1. a.clone()!=a,這也就意味着克隆對象和原始對象在java中是兩個不一樣的對象。
  2. a.clone().getClass == a.getClass(),克隆對象與原對象類型相同
  3. a.clone.equals(a),也就是說克隆對象完徹底全是原始對象的一個拷貝。此條件是非必需的
Cloneable接口

Object類沒有實現該接口,因此用戶若是沒有主動實現該接口時,調用clone()方法會報錯CloneNotSupportedException

Java實現步驟
  1. 實現Cloneable接口,這是步驟的關鍵之處。
  2. 重寫clone()方法,並聲明爲public,由於Object的該方法是protected的。
  3. 調用super.clone()來獲取新的克隆對象。在運行時刻,Object中的clone()識別出你要複製的是哪個對象,而後爲此對象分配空間,並進行對象的複製,將原始對象的內容一一複製到新對象的存儲空間中。
class A implements Cloneable {
    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

2.7.4 深複製和淺複製

淺複製
  • 被複制對象的全部變量都含有與原來的對象相同的值,而全部的對其餘對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所考慮的對象,而不復制它所引用的對象。
  • Object.clone()是淺複製。
深複製
  • 被複制對象的全部變量都含有與原來的對象相同的值,除去那些引用其餘對象的變量。那些引用其餘對象的變量將指向被複制過的新對象,而再也不是原有的那些被引用的對象。換言之,深複製把要複製的對象所引用的對象都複製了一遍。
  • 深複製要深刻到多少層是一個不易肯定到問題,須要特別注意
  • 深複製的過程當中可能出現循環引用到問題,須要當心處理
利用串行化進行深複製
  • 把對象寫到流裏的過程是串行化(Serilization)過程,可是在Java程序師圈子裏又很是形象地稱爲「冷凍」或者「醃鹹菜(picking)」
  • 把對象從流中讀出來的並行化(Deserialization)過程則叫作 「解凍」或者「回鮮(depicking)」過程。
  • 應當指出的是,寫在流裏的是對象的一個拷貝,而原對象仍然存在於JVM裏面,所以「醃鹹菜」的只是對象的一個拷貝,Java鹹菜還能夠回鮮。
  • 利用這個特性,在Java語言裏深複製一個對象,能夠先使對象實現Serializable接口,而後把對象(實際上只是對象的一個拷貝)寫到一個流裏,再從流裏讀出來,即可以克隆對象。
  • 這樣作的前提是對象以及對象內部全部引用到的對象都是可串行化的,不然,就須要仔細考察那些不可串行化的對象能否設成transient,從而將之排除在複製過程以外。
public Object deepClone() throws Exception {
        //將對象寫到流裏
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(this);

        //從流裏讀出來
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        return(oi.readObject());
    }
相關文章
相關標籤/搜索