工廠模式——烘烤OO披薩

一、簡單披薩店

我們在對象村開了家簡單的披薩店,下面是披薩店製作披薩的代碼:

public class PizzaStore {
	    Pizza orderPizza(String type) {
        Pizza pizza = null;
        //根據類型創建不同種類的披薩
        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        }else if (type.equals("greek")) {
            pizza = new GreekPizza();
        }
		//加工披薩步驟
        pizza.prepare();

        pizza.cut();

        pizza.box();
    }
}

爲什麼不要使用具體實例化(WHY)

代碼綁着具體類會導致代碼更耦合。當使用"new"時,就是在實例化一個具體類,一旦有變化或擴展,比如增加新的類型的披薩,就必須重新打開這段代碼進行檢查和修改。

如何使用簡單工廠替代具體實例化(HOW)

需要遵循"對擴展開放,對修改關閉”的原則解耦合,很明顯地,根據類型創建不同種類的披薩是變化的部分,而加工披薩是不會改變的部分。要把前者移到另一個類中,我們稱之爲工廠類。一旦有了工廠SimplePizzaFactory,披薩店orderPizza()就變成客戶,需要披薩的時候就叫工廠做一個,自己只需要調用prepare()、cut()、box()等加工方法。
建立一個簡單的披薩工廠:

public class SimplePizzaFactory {
    //只做一件事:創建披薩
    public Pizza createPizza(String type) {
        Pizza pizza = null;
        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        }else if (type.equals("greek")) {
            pizza = new GreekPizza();
        }
        return pizza;
    }
}

這樣做似乎只是把問題轉移到另一個類,但是工廠類可能有多個客戶,譬如宅急送類、披薩店菜單類等都會調用工廠類。當以後改變時,只需要修改工廠類一處。
重構pizzaStore類:

public class PizzaStore {
    SimplePizzaFactory simplePizzaFactory;

    //PizzaStore的構造器,參數爲工廠
    public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
        this.simplePizzaFactory = simplePizzaFactory;
    }
    
    public Pizza orderPizza(String type) {
        Pizza pizza;
        //使用工廠類來創建披薩,取代new具體實例化的方法。
        pizza = simplePizzaFactory.createPizza(type);
        
        pizza.prepare();
        
        pizza.cut();
        
        pizza.box();
        
        return pizza;
    }
}

什麼是簡單工廠(WHAT)

簡單工廠其實不是一個設計模式,而是一種編程習慣,它不是工廠模式(Factory Pattern)。披薩店的類圖如下:
在這裏插入圖片描述

二、披薩加盟店

簡單披薩店經營有成,現在大家都希望加盟,在自家附近開店,身爲公司CEO,你希望把控加盟店的質量,都用你的代碼。但是各地有風味差異,所以需要建立一個框架。

如何解決風味差異的問題(HOW)

有個做法可讓披薩製作侷限於PizzaStore類,同時加盟店又可以自由製作該區域風味。所要做的就是把createPizza()放回到PizzaStore類,不過要把它設置爲“抽象方法”,然後每個加盟店創建其子類。類圖及代碼如下:
在這裏插入圖片描述

public abstract class PizzaStorePlus {
    public Pizza orderPizza(String type) {
        Pizza pizza;
        //createPizza()方法從工廠類中移回PizzaStore
        pizza = createPizza(type);

        pizza.prepare();

        pizza.cut();

        pizza.box();

        return pizza;
    }
    //抽象的
    abstract Pizza createPizza(String type);
}
//紐約分店
public class NYPizzaStore extends PizzaStorePlus {
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        if (type.equals("cheese")) {
            pizza = new NYCheesePizza();
        }else if (type.equals("greek")) {
            pizza = new NYGreekPizza();
        }
        return pizza;
    }
}
//芝加哥分店
public class ChicagoPizzaStore extends PizzaStorePlus {
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        if (type.equals("cheese")) {
            pizza = new ChicagoCheesePizza();
        }else if (type.equals("greek")) {
            pizza = new ChicagoGreekPizza();
        }
        return pizza;
    }
}

披薩同樣需要定義爲抽象的,以便不同風味的具體子類繼承:

public abstract class Pizza {

    String name;
    //佐料
    List toppings = new ArrayList();

    void prepare() {
        System.out.println("準備烘焙:" + name);
        System.out.println("加佐料:");
        for(int i=0;i<toppings.size();i++) {
            System.out.println("   "+toppings.get(i));
        }
    }

    void cut() {
        System.out.println("切披薩");
    }

    void box() {
        System.out.println("裝披薩");
    }

    public String getName() {
        return name;
    }
}

public class NYCheesePizza extends Pizza{
    public NYCheesePizza() {
        name = "紐約Cheese披薩";
        toppings.add("意大利高級奶酪");
    }
}

public class ChicagoCheesePizza extends Pizza {
    public ChicagoCheesePizza() {
        name = "芝加哥Cheese披薩";
        toppings.add("意大利白乾奶酪");
    }

    void cut() {
        System.out.println("切成正方形");
    }
}

我們來訂購披薩:

public class PizzaDrive {
    public static void main(String[] args) {
        NYPizzaStore nyPizzaStore = new NYPizzaStore();
        ChicagoPizzaStore chicagoPizzaStore = new ChicagoPizzaStore();
        Pizza pizza = nyPizzaStore.orderPizza("cheese");
        System.out.println("訂了個:"+pizza.getName());
        pizza = chicagoPizzaStore.orderPizza("cheese");
        System.out.println("訂了個:"+pizza.getName());
    }
}

超類不用管細節,通過實例化正確的披薩子類,子類會自行照料一切。運行結果如下:
在這裏插入圖片描述

什麼是工廠方法模式(WHAT)

工廠方法模式的構成:

工廠方法模式由創建者類(PizzaStore及其子類)和產品類(Pizza及其子類)構成。工廠方法模式通過讓子類決定該創建的對象是什麼,來達到將對象創建的過程封裝的目的。

工廠方法模式的定義:

工廠方法模式定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。所謂的“決定”是指在編寫創建者類時,不需要知道實際創建的產品是哪一個,選擇使用哪個子類,自然就決定了實際創建的產品是什麼。工廠方法模式類圖如下:
在這裏插入圖片描述

依賴倒置原則(WHAT)

工廠方法模式其實遵循了依賴倒置原則。
如下,披薩店直接創建各種披薩對象,於是完全依賴於它們
在這裏插入圖片描述
我們需要遵循依賴倒置原則:要依賴抽象,不要依賴具體類。它是一個著名的OO設計原則。應用工廠方法模式後,高層組件(披薩店)和低層組件(各種披薩)都依賴Pizza抽象類,這就是倒置,類圖如下:
在這裏插入圖片描述
依賴倒置原則的指導方針:
變量不可以持有具體類的引用。如:使用new,可以使用工廠來替代它
不要讓類派生自具體類。請派生自抽象類。
不要覆蓋基類已實現的方法。

三、新增原料工廠

爲什麼要使用抽象工廠模式(WHY)

有一些加盟店使用低價原料來增加利潤,我們必須採取手段控制原料,以免損害品牌。你打算建造一家生產原料的工廠。原料包括醬料和芝士等。開始爲原料工廠定義一個接口:

public interface PizzaIngredientFactory {
    public Cheese createCheese();

    public Clams createClams();
}

紐約和芝加哥的原料工廠實現它:

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

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

public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
    @Override
    public Cheese createCheese() {
        return new ChicagoCheese();
    }

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

原料改變,將prepare()方法聲明爲抽象方法,重構披薩:

public abstract class Pizza {

    String name;
    
    Cheese cheese;
    
    Clams clams;
    //佐料
    List toppings = new ArrayList();

    abstract void prepare();

    void cut() {
        System.out.println("切披薩");
    }

    void box() {
        System.out.println("裝披薩");
    }

    public String getName() {
        return name;
    }

    public String toString() {

    }
}

我們曾經寫過NYCheesePizza和ChicagoCheesePizza,它們唯一的差別在於區域性原料不同,這個讓原料工廠處理差異就行,所以我們只需設計CheesePizza和ClamPizza:

public class CheesePizza extends Pizza {
    PizzaIngredientFactory factory;
    //要製作披薩,需要工廠提供原料。所以每個披薩類都需要從構造器參數得到一個工廠來製造原料。
    public CheesePizza(PizzaIngredientFactory factory) {
        this.factory = factory;
    }

    @Override
    void prepare() {
        System.out.println("Preparing " + name);

        cheese = factory.createCheese();
    }
}

public class ClamsPizza extends Pizza {
    PizzaIngredientFactory factory;
    //要製作披薩,需要工廠提供原料。所以每個披薩類都需要從構造器參數得到一個工廠來製造原料。
    public ClamsPizza(PizzaIngredientFactory factory) {
        this.factory = factory;
    }

    @Override
    void prepare() {
        System.out.println("Preparing " + name);

        clams = factory.createClams();
    }
}

加盟店需要使用對應原料工廠的披薩,我們將加盟店與當地原料工廠搭上關係:

public class NYPizzaStorePlus extends PizzaStorePlus {
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        if (type.equals("cheese")) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("紐約起司披薩");

        }else if (type.equals("clams")) {
            pizza = new ClamsPizza(ingredientFactory);
            pizza.setName("紐約蛤蜊披薩");
        }
        return pizza;
    }
}

public class ChicagoPizzaStorePlus extends PizzaStorePlus {
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        if (type.equals("cheese")) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("芝加哥起司披薩");

        }else if (type.equals("clams")) {
            pizza = new ClamsPizza(ingredientFactory);
            pizza.setName("芝加哥蛤蜊披薩");
        }
        return pizza;
    }
}

訂購披薩:

public class PizzaDrivePlus {
    public static void main(String[] args) {
        NYPizzaStorePlus nyPizzaStorePlus = new NYPizzaStorePlus();
        ChicagoPizzaStorePlus chicagoPizzaStorePlus = new ChicagoPizzaStorePlus();
        Pizza pizza = nyPizzaStorePlus.orderPizza("cheese");
        System.out.println("訂了個:"+pizza.getName());
        pizza = chicagoPizzaStorePlus.orderPizza("cheese");
        System.out.println("訂了個:"+pizza.getName());
    }
}

結果如下:
在這裏插入圖片描述

什麼是抽象工廠模式(WHAT)

抽象工廠模式提供一個抽象的接口,用於創建一組產品,而不需要知道實際產出的具體產品是什麼。另外抽象工廠的方法經常以工廠方法的方式實現。類圖如下:
在這裏插入圖片描述

工廠方法模式與抽象工廠模式異同(WHERE)

同:都是把應用程序從特定實現中解耦。
異:工廠方法使用繼承;抽象工廠使用組合。

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