工廠模式

GitHub代碼

場景描述:我們正在設計一個披薩店,因爲我們對披薩的操作有一套固有的流程,比如切割,裝盒等,但是我們有着非常多的披薩類型,風味不同,所以如何設計針對不同口味創建不同的披薩就成了整個程序的難點。

在這裏插入圖片描述

簡單工廠

爲了增加程序的彈性,並且避免頻繁修改代碼,我們將變化的部分拿出去,創建了一個簡單工廠類。

public class SimplePizzaFactory {
    public Pizza createPizza(String type){
        Pizza pizza = null;

        if (type.equals("cheese")){
            pizza = new CheesePizza();
        }else if (type.equals("pepperoni")){
            pizza = new PepperoniPizza();
        }else if (type.equals("clam")){
            pizza = new ClamPizza();
        }else if (type.equals("veggie")){
            pizza = new VeggiePizza();
        }

        return pizza;
    }
}

利用靜態方法定義一個簡單的工廠,這是很常見的技巧,常被稱爲靜態工廠。爲何使用靜態方法?因爲不需要使用創建對象的方法來實例化對象。getInstance()。

這樣我們的披薩店代碼就呼之欲出了。

public class PizzaStore {
    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory){
        this.factory = factory;
    }

    public Pizza orderPizza(String type){
        Pizza pizza;

        pizza = factory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

簡單工廠其實並不是一個設計模式,反而比較像是一種編程習慣。有些開發人員的確是把這個編程習慣誤認爲是“工廠模式”。

下面是這個簡單工廠的類圖:
在這裏插入圖片描述

工廠方法

新的問題:當我們出現新的加盟店時,如紐約和芝加哥,它們的口味不太相同,這樣我們就需要創建不同區域的特有工廠,然後再根據對應的口味生產特定的披薩。不過我們能夠看出來,不同的工廠中,通過特定口味生產披薩這個步驟都是相同的,只不過最後生產的具體披薩不同而已,所以我們是否可以將其抽象出來呢?
在這裏插入圖片描述
我們嘗試抽象披薩店的代碼:

public abstract class PizzaStore {

    public Pizza orderPizza(String type){
        Pizza pizza;

        pizza = createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    public abstract Pizza createPizza(String type);
}

然後針對不同地域去實現特定的製作方法
在這裏插入圖片描述
所有工廠模式都用來封裝對象的創建。工廠方法模式(Factory Method Pattern)通過讓子類決定該創建的對象是什麼,來達到將對象創建的過程封裝的目的。

讓我們來看一下通過這種方式來下單的流程如何:
在這裏插入圖片描述

工廠方法模式定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。

工廠方法模式能夠封裝具體類型的實例化。而所謂的“決定”,並不是指模式允許子類本身在運行時做決定,而是指編寫創建者類時,不需要知道實際創建的產品是哪一個。

我們看一下工廠方法模式的類圖:
在這裏插入圖片描述
再對比我們的披薩店,可以更加直觀的感受
在這裏插入圖片描述
簡單工廠把全部的事情,在一個地方都處理完了,然而工廠方法卻是創建一個框架,讓子類決定要如何實現。簡單工廠的做法,可以將對象的創建封裝起來,但是簡單工廠不具備工廠方法的彈性,因爲簡單工廠不能變更正在創建的產品。

抽象工廠

讓我們再看一個依賴性非常強的實現。
在這裏插入圖片描述
在這裏插入圖片描述
上圖看出了,依賴過多,所以會導致非常大的弊端,每次新增種類,不僅需要修改代碼,還造成了過多的依賴。

設計原則:要依賴抽象,不要依賴具體類。

這個原則說明了:不能讓高層組件依賴底層組件,而且,不管高層或底層組件,“兩者”都應該依賴於抽象。
這就是“依賴倒置原則”(Dependency Inversion Principle)。
在這裏插入圖片描述
下圖可以更加清晰地看出這句話的含義。
在這裏插入圖片描述
下面的指導方針,能幫你避免在OO設計中違反依賴倒置原則:

  • 變量不可以持有具體類的引用。
  • 不要讓類派生自具體類。
  • 不要覆蓋基類中已實現的方法。

當然只是儘量達到這個原則,而不是隨時都要遵守這個原則。

新的需求:我們又新增了新的需求,我們擁有不同地域的加盟店,並且它們使用的原料各不相同,而我們想要控制原料,而不是由他們自己提供原料,所以我們就需要一個新的設計來管理整個原料家族。

我們嘗試建造一個工廠來創建所有原料。

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

然後我們創建每個地域的工廠,如紐約的

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom()};
        return veggies;
    }

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

    @Override
    public Clams createClams() {
        return new FreshClams();
    }
}

這時我們的披薩變爲抽象的了。

public abstract class Pizza {
    public String name;
    public Dough dough;
    public Sauce sauce;
    public Veggies veggies[];
    public Cheese cheese;
    public Pepperoni pepperoni;
    public Clams clams;

    public abstract void prepare();

    public void bake(){
        System.out.println("Bake for 25 minutes at 350");
    }

    public void cut(){
        System.out.println("Cutting the pizza into diagonal slices");
    }

    public void box(){
        System.out.println("Place pizza in official PizzaStore box");
    }

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

    public String getName(){
        return name;
    }
}

此時我們的不同口味披薩不再需要地域性了,因爲地域性已經在原料中體現了。

public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

    public CheesePizza(PizzaIngredientFactory ingredientFactory){
        this.ingredientFactory = ingredientFactory;
    }

    public void prepare(){
        System.out.println("Preparing " + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

這時我們再實現不同區域的披薩店

public class NYPizzaStore extends PizzaStore {
    public Pizza createPizza(String item){
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        if (item.equals("cheese")){
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");
        }// 省略其他類型pizza

        return pizza;
    }
}

現在我們看一下一個披薩訂單都經歷了什麼
在這裏插入圖片描述
在這裏插入圖片描述
上述模式就是一種新的工廠模式。

抽象工廠模式提供一個接口,用於創建相關或依賴對象的家族,而不需要明確指定具體類。

我們看一下它的類圖:
在這裏插入圖片描述
比對一下我們的披薩店,更直觀的理解一下
在這裏插入圖片描述
總結
整個工廠方法模式,只不過就是通過子類來創建對象。用這種做法,客戶只需要知道他們所使用的抽象類型就可以了,而由子類來負責決定具體類型。換句話說,它將客戶從具體類型中解耦。
而抽象工廠模式,提供一個用來創建一個產品家族的抽象類型,這個類型的子類定義了產品被產生的方法。要想使用這個工廠,必須先實例化它,然後將它傳入一些針對抽象類型所寫的代碼中。換句話說,它將客戶從所使用的實際具體產品中解耦。

當你需要創建產品家族和想讓製造的相關產品集合起來時,你可以使用抽象工廠。
而當你需要把你的客戶代碼從需要實例化的具體類中解耦,或者如果你目前還不知道將來需要實例化哪些具體類時,就可以使用工廠方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章