天天看點

寂然解讀設計模式 - 工廠模式 - 抽象工廠模式

I walk very slowly, but I never walk backwards            

工廠模式 - 抽象工廠模式

​ 寂然

大家好~,我是寂然,本節課呢,我們接着來聊工廠模式的第三種,抽象工廠模式,以及工廠模式在 JDK源碼中的應用,最後,我們明确幾點工廠模式的注意事項,對工廠模式進行總結,那我們啟程吧

基本介紹 - 抽象工廠模式

抽象工廠模式:定義了一個 interface 用于建立相關或有依賴關系的對象簇,而無需指明具體的類

對象簇即一系列對象

抽象工廠模式可以将簡單工廠模式和工廠方法模式進行整合

案例回顧

怎麼了解上面的幾句話呢,首先,我們來回顧一下上節課的需求,然後我們通過類圖來詳細說明

有這樣一個披薩店的需求,披薩的種類很多(比如 GreekPizz、CheesePizz 等)

披薩的制作有 prepare,bake,cut,box 等,而且客戶在點披薩時,可以在不同城市點披薩, 比如北京點了芝士 pizza、水果 pizza 或者在上海點了芝士 pizza、水果 pizza等

要求:完成披薩店訂購功能,便于披薩種類的擴充,便于維護

類圖示範

OK,需求明确了,我們根據需求,繪制類圖,分析抽象工廠模式的實作思路,類圖如下圖所示

寂然解讀設計模式 - 工廠模式 - 抽象工廠模式

為什麼說抽象工廠模式可以将簡單工廠模式和工廠方法模式進行整合,首先仍然是簡單工廠的思想,由工廠類封裝執行個體化對象的行為,其次,createPizza()方法下沉到各個子類,又展現出工廠方法模式的特點

從設計層面看,抽象工廠模式就是對簡單工廠模式的改進,或者稱為進一步的抽象,将工廠抽象成兩層,一個AbsFactory(抽象工廠)和 具體實作的工廠子類,程式員可以根據建立對象類型使用對應的工廠子類,這樣将單個的簡單工廠類變成了工廠簇,更利于代碼的維護和擴充

解決方案四:抽象工廠模式

根據上面類圖的思路,我們對披薩訂購項目再次進行重構,使用抽象工廠模式來完成功能,示例代碼如下圖所示

//披薩抽象類
public abstract class Pizza {

    //定義一個屬性,披薩的名稱,并給定set方法
    protected String name;

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

    public String getName() {
        return name;
    }

    //認為不同的披薩,準備的原材料不同,是以定義成抽象方法
    public abstract void prepare();

    //烘焙方法
    public void bake() {

        System.out.println(name + "正在烘焙中");
    }

    //切割方法
    public void cut() {

        System.out.println(name + "正準備把披薩大卸八塊");
    }

    //打包方法
    public void box() {

        System.out.println(name + "将披薩打包給顧客");
    }
}

//北京的水果披薩
public class BJFruitPizza extends Pizza {

    @Override
    public void prepare() {

        setName("北京的水果披薩");
        System.out.println("北京的水果披薩正在準備原材料");
    }
}

//北京的希臘披薩
public class BJGreekPizza extends Pizza {

    @Override
    public void prepare() {

        setName("北京的希臘披薩");
        System.out.println("北京的希臘披薩正在準備原材料");
    }
}

//上海的水果披薩
public class SHFruitPizza extends Pizza {

    @Override
    public void prepare() {

        setName("上海的水果披薩");
        System.out.println("上海的水果披薩正在準備原材料");
    }
}

//上海的希臘披薩
public class SHGreekPizza extends Pizza {

    @Override
    public void prepare() {

        setName("上海的希臘披薩");
        System.out.println("上海的希臘披薩正在準備原材料");
    }
}

//抽象工廠模式-  抽象接口
public interface AbsFactory {

    //讓下面的工廠子類來具體實作
    public Pizza createPizza(String orderType);

}

//北京工廠子類
public class BJFactory implements AbsFactory{

    @Override
    public Pizza createPizza(String orderType) {

        Pizza pizza = null;

        if (orderType.equals("GreekPizza")){

            pizza = new BJGreekPizza();

        }else if (orderType.equals("FruitPizza")){

            pizza = new BJFruitPizza();
        }

        return pizza;
    }
}

//上海工廠子類
public class SHFactory implements AbsFactory{

    @Override
    public Pizza createPizza(String orderType) {
        Pizza pizza = null;

        if (orderType.equals("GreekPizza")){

            pizza = new SHGreekPizza();

        }else if (orderType.equals("FruitPizza")){

            pizza = new SHFruitPizza();
        }

        return pizza;
    }
}

//訂購Pizza類
public class OrderPizza {

    //聚合抽象工廠接口
    AbsFactory absFactory;

    public OrderPizza(AbsFactory absFactory){

        setAbsFactory(absFactory);

    }

    public void setAbsFactory(AbsFactory absFactory) {

        Pizza pizza = null;

        String orderType = ""; //使用者輸入

        this.absFactory = absFactory;

        while (true){

            orderType = getType();

            pizza = absFactory.createPizza(orderType);

            if (pizza != null){  //訂購成功,輸出制作過程

                pizza.prepare();

                pizza.bake();

                pizza.cut();

                pizza.box();

            } else{

                System.out.println("訂購失敗,咱這沒有,去别處把");

                break;
            }
        }
    }

    //定義方法擷取客戶希望訂購的披薩種類
    private String getType(){

        System.out.println("你想訂購那個種類的Pizza呢?");

        Scanner scanner = new Scanner(System.in);

        String str = scanner.next();

        return str;
    }
}

//用戶端示範
public class PizzaStore {

    public static void main(String[] args) {

        System.out.println("請輸入要訂購披薩的地點");

        String address = new Scanner(System.in).next();

        if (address.equals("北京")){

            new OrderPizza(new BJFactory());

        }else if (address.equals("上海")){

            new OrderPizza(new SHFactory());

        }else{

            System.out.println("該地點暫未開通訂購披薩功能");

        }
    }
}           

案例解讀

上面我們使用抽象工廠模式進行了代碼重構,其實抽象工廠模式代碼層面并不難,重點在于分析過程和類圖的關系要清晰,其實這兩種方式差别是有的,但是不大,核心思想大同小異,抽象工廠模式隻不過把工廠抽象成了兩層,而簡單工廠模式就是一個工廠類,但是大多數情況下,一個工廠類很難滿足複雜業務邏輯下多種不同類型的執行個體的建立,這個時候,使用抽象工廠模式搭建,會更顯得得心應手,是以,雖然工廠模式有三種,但是大家要根據實際情況靈活運用,切忌不要為了用而用

JDK源碼解析

Calendar 月曆類中,就使用到了簡單工廠模式,下面我們寫一段建議代碼,進入 getInstance() 進行檢視

//JDK源碼解析 - 工廠模式
public class Test {

    public static void main(String[] args) {

        Calendar calendar = Calendar.getInstance();

        System.out.println((calendar.get(Calendar.MONTH) + 1) + "月");//月份下标從0開始

        System.out.println(calendar.get(Calendar.DAY_OF_MONTH) + "日");

    }
}           

可以看到,源碼中首先定義 Calendar cal = null; 然後通過switch case語句,根據不同的種類,來建立不同的執行個體賦給 cal ,然後傳回,和我們簡單工廠模式中,根據客戶輸入類型的不同,訂購不同的披薩執行個體是非常類似的,如圖所示,是以JDK中 Calendar 類給我們傳回對象執行個體就是經典的簡單工廠模式

private static Calendar createCalendar(TimeZone zone,
                                           Locale aLocale)
    {
        CalendarProvider provider =
            LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                                 .getCalendarProvider();
        if (provider != null) {
            try {
                return provider.getInstance(zone, aLocale);
            } catch (IllegalArgumentException iae) {
                // fall back to the default instantiation
            }
        }

        Calendar cal = null;

        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
            if (caltype != null) {
                switch (caltype) {
                case "buddhist":
                cal = new BuddhistCalendar(zone, aLocale);
                    break;
                case "japanese":
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break;
                case "gregory":
                    cal = new GregorianCalendar(zone, aLocale);
                    break;
                }
            }
        }           

三種工廠模式對比

那到這裡,三種工廠模式已經全部結束了,那下面,我把三種模式對比着進行總結與回顧,友善大家融會貫通

簡單工廠模式
定義了一個建立對象的類,由這個類來封裝執行個體化對象的行為,可以根據參數的不同傳回不同類的執行個體

簡單工廠模式最大的優點在于工廠類中包含了必要的判斷邏輯,根據用戶端的條件動态的執行個體化相關的類,對于用戶端來說去除了對具體産品的依賴

工廠方法模式
定義一個建立對象的抽象方法,由子類決定要執行個體化的類,将對象的執行個體化推遲到子類

工廠方法使一個類的執行個體化,延遲到子類中去實作,可以在具體工廠産生執行個體的時候,做各種校驗,和産生規則,避免了直接用new帶來的不友善控制流程

抽象工廠模式
定義了一個 interface 用于建立相關或有依賴關系的一系列對象,而無需指明具體的類

進一步的抽象,将工廠抽象成兩層,一個AbsFactory(抽象工廠)和 具體實作的工廠子類,可以根據建立對象類型使用對應的工廠子類,這樣将單個的簡單工廠類變成了工廠簇,更利于代碼的維護和擴充

工廠模式意義

将執行個體化對象的代碼提取出來,放在一個類中統一維護和管理,達到和主項目的依賴關系的解耦,進而提高項目的擴充和可維護性,還是那句話,雖然工廠模式有三種,但是大家要根據實際情況靈活運用,切忌不要為了用而用

下節預告

OK,工廠模式的内容到了這裡就告一段落了,下一節,我們從大家最感興趣的履歷入手,開啟原型模式的學習,最後,希望大家在學習的過程中,能夠感覺到設計模式的有趣之處,高效而愉快的學習,那我們下期見~