1. 程式人生 > >headfirst設計模式(5)—工廠模式體系分析及抽象工廠模式

headfirst設計模式(5)—工廠模式體系分析及抽象工廠模式

先編一個這麼久不寫的理由

上週我終於鼓起勇氣翻開了headfirst設計模式這本書,看看自己下一個設計模式要寫個啥,然後,我終於知道我為啥這麼久都沒寫設計模式了,headfirst的這個抽象工廠模式,額,我看了好幾次,都不太理解。

在我的印象中,簡單工廠,工廠方法,抽象工廠,這三個東西應該是層層遞進的,然後我帶著這個思路去看,emmmm,真的沒看懂,還好最近又補了一遍《大話設計模式》,揣著剛剛溫習了的新知識,然後又上了headfirst這條船,我感覺我這次應該是看懂了。

所以不寫並不是因為忙,也不是因為懶,什麼上分和吃雞,我根本都沒有聽說過,和一個996的碼農說這些,表示很扎心。

看完了工廠模式以後,發現後面居然是單例模式,兄弟,你是認真的嗎?(淚流滿面)

抽象工廠舉例分析

首先,我去看了一下這本書配套的原始碼,嗯,抽象工廠的類,感覺太多太多了,帶上測試類一共是35個,這要是貼出來,可以水很大的篇幅了,但是我是那種人嗎?當然不是!

碰到這種情況當然是直接給地址,有需要的小夥伴可以自己去看

github地址:https://github.com/bethrobson/Head-First-Design-Patterns

工廠模式遞進體系分析

先捋一捋書中描述的抽象工廠是個什麼意思,要說清楚這個,就必須先說一下它整個工廠模式的敘述的遞進關係,具體如下:

1,先拿一個賊簡單的建立Pizza的簡單工廠類PizzaStore,來忽悠我,這章很簡單,來學學吧,這章原始碼只有8個類

2,興沖沖的看完了第一部分,當然是一鼓作氣的看第二部分,第二部分總結起來大概是講這麼一個事情:

  1)我們公司在紐約的那個旗艦店(PizzaStore)生意不錯,現在我們要開一個分店,名字叫芝加哥披薩店(ChicagoPizzaStore)

  2)分店開了以後,原來的先進經驗不能丟啊,所以PizzaStore被抽象出來了,搞成了一個抽象類,作為基礎技術儲備

  3)PizzaStore變成了公司的基礎部門,專門指導披薩製作的流程

  4)旗艦店沒辦法啊,一方面自己的名字被佔用了,一方面規範管理的風也吹來了,就改名字叫紐約披薩店(NYPizzaStore),旗下的所有披薩也順勢把名字也改了

  5)新開起來的芝加哥披薩店,倚靠了公司的技術實力,copy了一份旗艦店的選單,但是也要照顧本地人的口味啊,所以就因地制宜,開發了自己的產品

  整個事情就是這樣,涉及到的類有:pizza抽象類(1個),PizzaStore抽象類(1個),實體披薩店(2個),實體店的各類Pizza(8個),測試類(1個)

  總的來說,這一部分的遞進關係很棒,也能很好的體現,簡單工廠和工廠方法最大的不同,工廠方法可以對工廠類做到:開閉原則

3,工廠方法扯完了,就來看一下,抽象工廠嘛,這一波操作我當時著實沒有看懂,就一直擱置了,為什麼當時沒看懂呢?

對於正常的抽象工廠的講解套路來說應該是這個樣子的:新增加一個產品線(抽象類,實現類),重新規劃一下Factory的介面和實現類(Factory裡新加一個介面,實現類新增實現)

它沒有這樣搞,它直接根據披薩原料,重新抽象了一套抽象工廠模式的體系出來,不得不說,這個比剛才說的那個新增產品線的例子漂亮太多了,原因有以下幾點:

  1)直接新增一個產品線的這種操作,雖然更容易理解,但是會給人一個誤導:我在原來Factory新增介面的這個操作,是符合設計模式,符合抽象工廠模式的

  2)抽象工廠的缺點是什麼?缺點就是,Factory的介面,在實際使用的時候,幾乎是無法新增產品的,修改太多了。所以它選擇了使用原料這套新體系來講解,更加符合實際

當然,它的例子也不是沒有缺點,個人感覺它的缺點有以下幾點:

  1)產生了類太多了,35個(上面有說過),對新手不友好,看到這個量級,稍微往後一翻又是一個單例模式,想著這個東西又不怎麼能用上,有求生慾望的,都不會在放棄的邊緣瘋狂試探……

  2)對它本身的工廠方法模式的體系(PizzaStore體系),也有很大量的修改,集中體現在各類Pizza實現類的縮減(取消了按店鋪名稱來建立的各類披薩,轉而使用了簡單工廠模式裡面的名字),Pizza的方法實現也有很多修改,主要是為了支援原料體系,當然,雖然產生了很多的修改,但是對外部提供的介面是沒有影響的,換句話說,雖然實現有了翻天覆地的變化,但是顧客還是無感知的,這個從測試程式碼就能看出來:

工廠方法模式測試類:

public class PizzaTestDrive {
 
    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        PizzaStore chicagoStore = new ChicagoPizzaStore();
 
        Pizza pizza = nyStore.orderPizza("cheese");
        System.out.println("Ethan ordered a " + pizza.getName() + "\n");
 
        pizza = chicagoStore.orderPizza("cheese");
        System.out.println("Joel ordered a " + pizza.getName() + "\n");

        pizza = nyStore.orderPizza("clam");
        System.out.println("Ethan ordered a " + pizza.getName() + "\n");
 
        pizza = chicagoStore.orderPizza("clam");
        System.out.println("Joel ordered a " + pizza.getName() + "\n");

        pizza = nyStore.orderPizza("pepperoni");
        System.out.println("Ethan ordered a " + pizza.getName() + "\n");
 
        pizza = chicagoStore.orderPizza("pepperoni");
        System.out.println("Joel ordered a " + pizza.getName() + "\n");

        pizza = nyStore.orderPizza("veggie");
        System.out.println("Ethan ordered a " + pizza.getName() + "\n");
 
        pizza = chicagoStore.orderPizza("veggie");
        System.out.println("Joel ordered a " + pizza.getName() + "\n");
    }
}
View Code

抽象工廠測試類:

public class PizzaTestDrive {
 
    public static void main(String[] args) {
        PizzaStore nyStore = new NYPizzaStore();
        PizzaStore chicagoStore = new ChicagoPizzaStore();
 
        Pizza pizza = nyStore.orderPizza("cheese");
        System.out.println("Ethan ordered a " + pizza + "\n");
 
        pizza = chicagoStore.orderPizza("cheese");
        System.out.println("Joel ordered a " + pizza + "\n");

        pizza = nyStore.orderPizza("clam");
        System.out.println("Ethan ordered a " + pizza + "\n");
 
        pizza = chicagoStore.orderPizza("clam");
        System.out.println("Joel ordered a " + pizza + "\n");

        pizza = nyStore.orderPizza("pepperoni");
        System.out.println("Ethan ordered a " + pizza + "\n");
 
        pizza = chicagoStore.orderPizza("pepperoni");
        System.out.println("Joel ordered a " + pizza + "\n");

        pizza = nyStore.orderPizza("veggie");
        System.out.println("Ethan ordered a " + pizza + "\n");
 
        pizza = chicagoStore.orderPizza("veggie");
        System.out.println("Joel ordered a " + pizza + "\n");
    }
}
View Code

是一個非常讚的地方

抽象工廠實現過程

首先,我們從原料的工廠類入手:

public interface PizzaIngredientFactory {
 
    Dough createDough();
    Sauce createSauce();
    Cheese createCheese();
    Veggies[] createVeggies();
    Pepperoni createPepperoni();
    Clams createClam();
 
}

一共有6種原料可以建立,這個也是它原始碼類多的原因之一,由於大多數程式碼都是相似的,所以這裡也就不一一的列舉了只列舉流程相關的實現

先看醬油(Sauce)相關的介面和實現:

public interface Sauce {
    String toString();
}

PlumTomatoSauce:

public class PlumTomatoSauce implements Sauce {
    public String toString() {
        return "Tomato sauce with plum tomatoes";
    }
}
View Code

MarinaraSauce:

public class MarinaraSauce implements Sauce {
    public String toString() {
        return "Marinara Sauce";
    }
}
View Code

然後看看麵糰的相關介面和實現:

public interface Dough {
    String toString();
}

ThickCrustDough:

public class ThickCrustDough implements Dough {
    public String toString() {
        return "ThickCrust style extra thick crust dough";
    }
}
View Code

ThinCrustDough:

public class ThinCrustDough implements Dough {
    public String toString() {
        return "Thin Crust Dough";
    }
}
View Code

以上,是抽象工廠的基礎:產品族的工廠類,產品介面(或者是抽象類),產品的具體實現類請忽略它只是為了方便理解放到一起的

列舉了這個,再來看看具體的原料工廠:

ChicagoPizzaIngredientFactory:

public class ChicagoPizzaIngredientFactory 
    implements PizzaIngredientFactory 
{

    //我是麵糰,我是這裡
    public Dough createDough() {
        return new ThickCrustDough();
    }

    //我是醬油,我在這裡
    public Sauce createSauce() {
        return new PlumTomatoSauce();
    }

    public Cheese createCheese() {
        return new MozzarellaCheese();
    }

    public Veggies[] createVeggies() {
        Veggies veggies[] = { new BlackOlives(), 
                              new Spinach(), 
                              new Eggplant() };
        return veggies;
    }

    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    public Clams createClam() {
        return new FrozenClams();
    }
}
View Code

NYPizzaIngredientFactory:

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

    //我是麵糰,我在這裡
    public Dough createDough() {
        return new ThinCrustDough();
    }

    //我是醬油,我在這裡
    public Sauce createSauce() {
        return new MarinaraSauce();
    }
 
    public Cheese createCheese() {
        return new ReggianoCheese();
    }
 
    public Veggies[] createVeggies() {
        Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
        return veggies;
    }
 
    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    public Clams createClam() {
        return new FreshClams();
    }
}
View Code

實現也是很簡單的,這裡為了不佔篇幅就隱藏了,有需要的自己點開看,接下來看一個這個原料體系的建立,需要修改的體現:

1,各類Pizza的實現類,需要持有PizzaIngredientFactory物件,並且prepare方法會有修改

public class ClamPizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;
 
    public ClamPizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }
 
    void prepare() {
        System.out.println("Preparing " + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
        clam = ingredientFactory.createClam();
    }
}

2,具體的pizza工廠的修改:

public class ChicagoPizzaStore extends PizzaStore {

    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        //建立各自的原料工廠
        PizzaIngredientFactory ingredientFactory =
        new ChicagoPizzaIngredientFactory();

        if (item.equals("cheese")) {
            //建立具體pizza的時候傳入原料工廠
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("Chicago Style Cheese Pizza");

        } else if (item.equals("veggie")) {

            pizza = new VeggiePizza(ingredientFactory);
            pizza.setName("Chicago Style Veggie Pizza");

        } else if (item.equals("clam")) {

            pizza = new ClamPizza(ingredientFactory);
            pizza.setName("Chicago Style Clam Pizza");

        } else if (item.equals("pepperoni")) {

            pizza = new PepperoniPizza(ingredientFactory);
            pizza.setName("Chicago Style Pepperoni Pizza");

        }
        return pizza;
    }
}

以上就是抽象工廠類的實現套路,以及在head first中,對原工廠方法模式具體影響的敘述,貼程式碼是不可能貼程式碼的,這輩子都不可能貼程式碼的