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

一、簡單工廠模式(Simple Factory Pattern)
 
簡單工廠模式專門定義一個類來負責創建其他類的實例,被創建的實例通常都具有共同的父類。
 
1、簡單工廠模式結構圖
 
Factory:工廠類,簡單工廠模式的核心,負責創建所有實例的內部邏輯。工廠創建的產品類的方法可以直接被外界調用,創建所需的產品對象。
IProduct:抽象產品類,簡單工廠模式所創建的所有對象的父類,負責描述所有實例所共有的公共接口。
Product:具體產品類,見識簡單工廠模式的創建目標。
 
2、簡單工廠模式的簡單實現
 
我們用生產電腦來舉例,假設有一個電腦的代工生產商,目前已經生產聯想電腦了,隨着業務的拓展,這個產商還要生產惠普和華碩的電腦,這樣我們就需要用一個單獨的類來專門生產電腦,這就用到了簡單工廠模式。
(1)創建抽象產品類
public abstract class Computer {
    /**
     * 產品的抽象方法,由具體的產品類去實現
     */
    public abstract void start();
}

(2)創建具體產品類

//聯想電腦
public class LenovoComputer extends Computer{
    @Override
    public void start() {
        System.out.println("聯想電腦啓動");
    }
}
//惠普電腦
public class HpComputer extends Computer{
    @Override
    public void start() {
        System.out.println("惠普電腦啓動");
    }
}
//華碩電腦
public class AsusComputer extends Computer{
    @Override
    public void start() {
        System.out.println("華碩電腦啓動");
    }
}

(3)創建工程類

接下來創建一個工廠類,它提供了一個靜態方法createComputer用來生產電腦。你只需要傳入你想生產的電腦的品牌,它就會實例化相應品牌的電腦對象:
public class ComputerFactory {
    public static Computer createComputer(String type){
        Computer mComputer=null;
        switch (type) {
            case "lenovo":
                mComputer=new LenovoComputer();
               break;
            case "hp":
                mComputer=new HpComputer();
                break;
            case "asus":
                mComputer=new AsusComputer();
                break;
        }
        return mComputer;
    }
}

(4)客戶端調用工廠類

客戶端調用工廠類,傳入“hp”生產出惠普電腦並調用該電腦對象的start方法:
public class CreatComputer {
    public static void main(String[]args){
      ComputerFactory.createComputer("hp").start();
    }
}

3、優點和缺點

(1)優點
  • 工廠類含有必要的判斷邏輯,可以決定在什麼時候創建哪一個產品類的實例,客戶端可以免除直接創建產品對象的責任,而僅僅“消費”產品;簡單工廠模式通過這種做法實現了對責任的分割,它提供了專門的工廠類用於創建對象。
  • 客戶端無須知道所創建的具體產品類的類名,只需要知道具體產品類所對應的參數即可,對於一些複雜的類名,通過簡單工廠模式可以減少使用者的記憶量。
  • 通過引入配置文件,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產品類,在一定程度上提高了系統的靈活性。
(2)缺點
  • 由於工廠類集中了所有產品創建邏輯,一旦不能正常工作,整個系統都要受到影響。
  • 使用簡單工廠模式將會增加系統中類的個數,在一定程序上增加了系統的複雜度和理解難度。
  • 系統擴展困難,一旦添加新產品就不得不修改工廠邏輯,同樣破壞了“開閉原則”;在產品類型較多時,有可能造成工廠邏輯過於複雜,不利於系統的擴展和維護。
  • 簡單工廠模式由於使用了靜態工廠方法,造成工廠角色無法形成基於繼承的等級結構。
4、適用場景
在以下情況下可以使用簡單工廠模式:
  • 工廠類負責創建的對象比較少:由於創建的對象較少,不會造成工廠方法中的業務邏輯太過複雜。
  • 客戶端只知道傳入工廠類的參數,對於如何創建對象不關心:客戶端既不需要關心創建細節,甚至連類名都不需要記住,只需要知道類型所對應的參數。
5、模式應用
(1)JDK類庫中廣泛使用了簡單工廠模式,如工具類java.text.DateFormat,它用於格式化一個本地日期或者時間。
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale locale);

(2)獲取不同加密算法的密鑰生成器。

KeyGenerator keyGen=KeyGenerator.getInstance("DESede");

二、工廠方法模式(Factory Method Pattern)

1、工廠方法模式結構圖

在工廠方法模式結構圖中包含如下幾個角色:

  • Factory(抽象工廠類):在抽象工廠類中,聲明瞭工廠方法(Factory Method),用於返回一個產品。抽象工廠是工廠方法模式的核心,所有創建對象的工廠類都須實現該接口。

  • ConcreteFactory(具體工廠類):它是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,並可由客戶端調用,返回一個具體產品類的實例。

  • Product(抽象產品類):它是定義產品的接口,是工廠方法模式所創建對象的超類型,也就是產品對象的公共父類。

  • (ConcreteProduct具體產品類):它實現了抽象產品接口,某種類型的具體產品由專門的具體工廠創建,具體工廠和具體產品之間一一對應。

與簡單工廠模式相比,工廠方法模式最重要的區別是引入了抽象工廠角色,抽象工廠可以是接口,也可以是抽象類或者具體類。

2、工廠方法模式的簡單實現
(1)抽象產品
public abstract class Cake {
    void prepare(){
        System.out.println("步驟:step 1......");
        System.out.println("步驟:step 2......");
        System.out.println("步驟:step 3......");
        System.out.println("步驟:step 4......");
    }
    void bake(){
        System.out.println("烘焙:bake");
    }
    void box(){
        System.out.println("裝盒:box");
    }
}

(2)具體產品

public class CenterCheeseCake extends Cake {
    public CenterCheeseCake(){
        name = "center cheese cake";
    }
    @Override
    public void bake(){
        System.out.println("不用烘箱,我要用火烤!");
    }
}

public class CollegeFruitCake extends Cake {
    public CollegeFruitCake(){
        name = "center fruit cake";
    }
    @Override
    public void box(){
        System.out.println("不用圓盒子打包,我愛國,用五角星盒子!");
    }
}

(3)抽象工廠

public abstract class CakeStore {
    public Cake orderCake(String type) {
        Cake cake;
        cake = createCake(type);
        cake.bake();
        cake.box();
        return cake;
    }

    protected abstract Cake createCake(String type);
}

(4)具體工廠類

在抽象工廠中聲明瞭工廠方法但並未實現工廠方法,具體產品對象的創建由其子類負責,客戶端針對抽象工廠編程,可在運行時再指定具體工廠類,具體工廠類實現了工廠方法,不同的具體工廠可以創建不同的具體產品。
public class CenterCakeStore extends CakeStore {
    @Override
    protected Cake createCake(String type) {
        Cake cake = null;
        if ("cheese".equals(type)) {
            cake = new CenterCheeseCake();
        } else if ("fruit".equals(type)) {
            cake = new CenterFruitCake();
        } else if ("cream".equals(type)) {
            cake = new CenterCreamCake();
        }
        return cake;
    }
}

public class CollegeCakeStore extends CakeStore {
    @Override
    protected Cake createCake(String type) {
        Cake cake = null;
        if ("cheese".equals(type)) {
            cake = new CollegeCheeseCake();
        } else if ("fruit".equals(type)) {
            cake = new CollegeFruitCake();
        } else if ("cream".equals(type)) {
            cake = new CollegeCreamCake();
        }
        return cake;
    }
}

3、優點和缺點

(1)優點
  • 工廠方法用來創建客戶端所需要的產品,同時還向客戶端隱藏了哪種具體產品類將被實例化這一細節,客戶端只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體產品類的類名。
  • 基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵。它能夠使工廠可以自主確定創建何種產品對象,而如何創建這個對象的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱爲多態工廠模式,是因爲所有的具體工廠類都具有同一抽象父類。
  • 使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就可以了。這樣,系統的可擴展性也就變得非常好,完全符合“開閉原則”。
(2)缺點
  • 在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
  • 由於考慮到系統的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能需要用到DOM、反射等技術,增加了系統的實現難度
4、適用場景
  • 一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建;客戶端需要知道創建具體產品的工廠類。
  • 一個類通過其子類來指定創建哪個對象:在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。
  • 將創建對象的任務委託給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類創建產品子類,需要時再動態指定,可將具體工廠類的類名存儲在配置文件或數據庫中。
5、模式擴展
  • 使用多個工廠方法:在抽象工廠角色中可以定義多個工廠方法,從而使具體工廠角色實現這些不同的工廠方法,這些方法可以包含不同的業務邏輯,以滿足對不同的產品對象的需求。
  • 產品對象的重複使用:工廠對象將已經創建過的產品保存到一個集合(如數組、List等)中,然後根據客戶對產品的請求,對集合進行查詢。如果有滿足要求的產品對象,就直接將該產品返回客戶端;如果集合中沒有這樣的產品對象,那麼就創建一個新的滿足要求的產品對象,然後將這個對象在增加到集合中,再返回給客戶端。
  • 多態性的喪失和模式的退化:如果工廠僅僅返回一個具體產品對象,便違背了工廠方法的用意,發生退化,此時就不再是工廠方法模式了。一般來說,工廠對象應當有一個抽象的父類型,如果工廠等級結構中只有一個具體工廠類的話,抽象工廠就可以省略,也將發生了退化。當只有一個具體工廠,在具體工廠中可以創建所有的產品對象,並且工廠方法設計爲靜態方法時,工廠方法模式就退化成簡單工廠模式。

三、 抽象工廠模式(Abstrract Factory Pattern

抽象工廠模式可以說是是工廠方法模式的升級版,當需要創建的產品有多個產品線(產品族)時使用抽象工廠模式是比較好的選擇。我們一直都是生產聯想和惠普的電腦,但是電腦也有多個產品線:臺式機、筆記本和平板等等,聯想和惠普都在生產這些不同產品線上的電腦,使用工廠方法模式已經滿足不了需求。
爲創建一組相關或者相互依賴的對象提供一個接口,而無需指定它們的具體類。

1、抽象工廠模式結構圖

 

在抽象工廠模式中有如下角色:
  • AbstractFactory:抽象工廠,它聲明瞭用來創建不同產品的方法。
  • ConcreteFactory:具體工廠,實現抽象工廠中定義的創建產品的方法。
  • AbstractProduct:抽象產品,爲每種產品聲明業務方法。比如上圖的AbstractProductA和 AbstractProductB。
  • ConcreteProduct:具體產品,定義具體工廠生產的具體產品,並實現抽象產品中定義的業務方法。
2、工廠方法模式的簡單實現
聯想和惠普生產的電腦分爲了兩個產品線,一個臺式機,一個是筆記本。爲了解決增加產品線的問題,我們用抽象工廠模式來進行實現。
(1)定義抽象產品類
兩個抽象產品類分別爲DesktopComputer和NotebookComputer ,用來定義兩個產品線:臺式機和筆記本。它們都定義了start方法,用來啓動電腦。
public abstract class DesktopComputer {
  public abstract void start();
}

public abstract class NotebookComputer {
   public abstract void start();
}

(2)具體產品類

public class LenovoDesktopComputer extends DesktopComputer {
    @Override
    public void start() {
        System.out.println("聯想臺式電腦啓動");
    }
}

public class HpDesktopComputer extends DesktopComputer {
    @Override
    public void start() {
        System.out.println("惠普臺式電腦啓動");
    }
}

public class LenovoNotebookComputer extends NotebookComputer {
    @Override
    public void start() {
        System.out.println("聯想筆記本電腦啓動");
    }
}

public class HpNotebookComputer extends NotebookComputer {
    @Override
    public void start() {
        System.out.println("惠普筆記本電腦啓動");
    }
}

(3)抽象工廠

public abstract class ComputerFactory {
    public abstract DesktopComputer createDesktopComputer();
    public abstract NotebookComputer createNotebookComputer();
}

(4)具體工廠

public class LenovoFactory extends ComputerFactory {
    @Override
    public DesktopComputer createDesktopComputer() {
        return new LenovoDesktopComputer();
    }

    @Override
    public NotebookComputer createNotebookComputer() {
        return new LenovoNotebookComputer();
    }
}

public class HpFactory extends ComputerFactory {
    @Override
    public DesktopComputer createDesktopComputer() {
        return new HpDesktopComputer();
    }

    @Override
    public NotebookComputer createNotebookComputer() {
        return new HpNotebookComputer();
    }
}

(5)客戶端調用

public class Client {
    public static void main(String[]args) {
        ComputerFactory lenocoFactory=new LenovoFactory();
        lenocoFactory.createDesktopComputer().start();
        lenocoFactory.createNotebookComputer().start();
        ComputerFactory hpFactory=new HpFactory();
        hpFactory.createDesktopComputer().start();
        hpFactory.createNotebookComputer().start();
    }
}

接下來給出這個例子的UML圖,更便於理解:

3、優點和缺點
 
(1)優點
  • 抽象工廠模式隔離了具體類的生成,使得客戶並不需要知道什麼被創建。由於這種隔離,更換一個具體工廠就變得相對容易。所有的具體工廠都實現了抽象工廠中定義的那些公共接口,因此只需改變具體工廠的實例,就可以在某種程度上改變整個軟件系統的行爲。另外,應用抽象工廠模式可以實現高內聚低耦合的設計目的,因此抽象工廠模式得到了廣泛的應用。
  • 當一個產品族中的多個對象被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的對象。這對一些需要根據當前環境來決定其行爲的軟件系統來說,是一種非常實用的設計模式。
  • 增加新的具體工廠和產品族很方便,無須修改已有系統,符合“開閉原則”。
(2)缺點
  • 在添加新的產品對象時,難以擴展抽象工廠來生產新種類的產品,這是因爲在抽象工廠角色中規定了所有可能被創建的產品集合,要支持新種類的產品就意味着要對該接口進行擴展,而這將涉及到對抽象工廠角色及其所有子類的修改,顯然會帶來較大的不便。
  • 開閉原則的傾斜性
    (1) 增加產品族:對於增加新的產品族,抽象工廠模式很好地支持了“開閉原則”,只需要增加具體產品並對應增加一個新的具體工廠,對已有代碼無須做任何修改。
    (2) 增加新的產品等級結構:對於增加新的產品等級結構,需要修改所有的工廠角色,包括抽象工廠類,在所有的工廠類中都需要增加生產新產品的方法,違背了“開閉原則”。
 
正因爲抽象工廠模式存在“開閉原則”的傾斜性,它以一種傾斜的方式來滿足“開閉原則”,爲增加新產品族提供方便,但不能爲增加新產品結構提供這樣的方便,因此要求設計人員在設計之初就能夠全面考慮,不會在設計完成之後向系統中增加新的產品等級結構,也不會刪除已有的產品等級結構,否則將會導致系統出現較大的修改,爲後續維護工作帶來諸多麻煩。
 
4、適用場景
在以下情況下可以考慮使用抽象工廠模式:
  • 一個系統不應當依賴於產品類實例如何被創建、組合和表達的細節,這對於所有類型的工廠模式都是很重要的,用戶無須關心對象的創建過程,將對象的創建和使用解耦。
  • 系統中有多於一個的產品族,而每次只使用其中某一產品族。可以通過配置文件等方式來使得用戶可以動態改變產品族,也可以很方便地增加新的產品族。
  • 屬於同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。同一個產品族中的產品可以是沒有任何關係的對象,但是它們都具有一些共同的約束,如同一製作水果蛋糕用的水果--草莓和芒果,草莓和芒果之間沒有直接關係,但它們都是屬於水果。
  • 產品等級結構穩定,設計完成之後,不會向系統中增加新的產品等級結構或者刪除已有的產品等級結構。
5、模式擴展
 
“開閉原則”的傾斜性
“開閉原則”要求系統對擴展開放,對修改封閉,通過擴展達到增強其功能的目的。對於涉及到多個產品族與多個產品等級結構的系統,其功能增強包括兩方面:
  • 增加產品族:對於增加新的產品族,工廠方法模式很好的支持了“開閉原則”,對於新增加的產品族,只需要對應增加一個新的具體工廠即可,對已有代碼無須做任何修改。
  • 增加新的產品等級結構:對於增加新的產品等級結構,需要修改所有的工廠角色,包括抽象工廠類,所有的工廠類中都要增加生產新產品的方法,不能很好地支持“開閉原則”。
工廠模式的退化
  • 當抽象工廠模式中每一個具體工廠類只創建一個產品對象,也就是隻存在一個產品等級結構時,抽象工廠模式退化成工廠方法模式;
  • 當工廠方法模式中抽象工廠與具體工廠合併,提供一個統一的工廠來創建產品對象,並將創建對象的工廠方法設計爲靜態方法時,工廠方法模式退化成簡單工廠模式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章