你好!今天我們繼續學習設計模式,我們將討論工廠方法模式。 設計模式:工廠方法 - 1 您將了解它是什麼以及該模式適用於哪些任務。我們將在實踐中考慮這種設計模式並研究其結構。為確保一切都清楚,您需要了解以下主題:
  1. Java中的繼承。
  2. Java 中的抽象方法和類

工廠方法解決什麼問題?

所有的工廠設計模式都有兩類參與者:創建者(工廠本身)和產品(工廠創建的對象)。想像一下以下情況:我們有一家生產 CodeGym 品牌汽車的工廠。它知道如何創建具有各種車身類型的汽車模型:
  • 轎車
  • 旅行車
  • 雙門轎車
我們的業務蒸蒸日上,以至於有一天我們收購了另一家汽車製造商——OneAuto。作為明智的企業主,我們不想失去任何 OneAuto 客戶,因此我們面臨重組生產的任務,以便我們能夠生產:
  • CodeGym 轎車
  • CodeGym 旅行車
  • CodeGym 跑車
  • OneAuto轎車
  • OneAuto旅行車
  • OneAuto轎跑車
如您所見,我們現在有兩組產品,而不是一組產品,它們在某些細節上有所不同。工廠方法設計模式適用於當我們需要創建不同的產品組時,每個產品組都有一些特定的特徵。我們將在實踐中考慮此模式的指導原則,使用我們在之前課程之一中創建的咖啡店示例,逐漸從簡單過渡到復雜。

關於工廠模式的一點

讓我提醒你,我們之前建立了一個小型虛擬咖啡店。在一個簡單工廠的幫助下,我們學會瞭如何製作不同類型的咖啡。今天我們將重做這個例子。讓我們回想一下我們的咖啡店的樣子,裡面有一個簡單的工廠。我們有咖啡課:

public class Coffee {
    public void grindCoffee(){
        // Grind the coffee
    }
    public void makeCoffee(){
        // Brew the coffee
    }
    public void pourIntoCup(){
        // Pour into a cup
    }
}
以及我們工廠可以生產的特定類型咖啡對應的幾個子類:

public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
我們創建了一個枚舉,以便於下訂單:

public enum CoffeeType {
    ESPRESSO,
    AMERICANO,
    CAFFE_LATTE,
    CAPPUCCINO
}
咖啡工廠本身是這樣的:

public class SimpleCoffeeFactory {
    public Coffee createCoffee(CoffeeType type) {
        Coffee coffee = null;

        switch (type) {
            case AMERICANO:
                coffee = new Americano();
                break;
            case ESPRESSO:
                coffee = new Espresso();
                break;
            case CAPPUCCINO:
                coffee = new Cappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new CaffeLatte();
                break;
        }
        
        return coffee;
    }
}
最後,咖啡店本身看起來像這樣:

public class CoffeeShop {

    private final SimpleCoffeeFactory coffeeFactory;

    public CoffeeShop(SimpleCoffeeFactory coffeeFactory) {
        this.coffeeFactory = coffeeFactory;
    }

    public Coffee orderCoffee(CoffeeType type) {
        Coffee coffee = coffeeFactory.createCoffee(type);
        coffee.grindCoffee();
        coffee.makeCoffee();
        coffee.pourIntoCup();

        System.out.println("Here's your coffee! Thanks! Come again!");
        return coffee;
    }
}

使一個簡單的工廠現代化

我們的咖啡店經營得很好。如此之多,以至於我們正在考慮擴大規模。我們想開一些新的地點。我們大膽進取,所以我們不會搞無聊的咖啡店。我們希望每家商店都有一個特別的轉折點。因此,首先,我們將開設兩個地點:一個意大利和一個美國。這些變化不僅會影響室內設計,還會影響提供的飲品:
  • 在意式咖啡店,我們會選用意大利獨家咖啡品牌,經過特殊的研磨和烘焙。
  • 美國分店的份量更大,我們將在每份訂單中提供棉花糖。
唯一不變的是我們的商業模式,它已經證明自己是優秀的。就代碼而言,這就是發生的事情。我們有 4 個類對應於我們的產品:

public class Americano extends Coffee {}
public class Cappuccino extends Coffee {}
public class CaffeLatte extends Coffee {}
public class Espresso extends Coffee {}
但現在我們將有 8 個:

public class ItalianStyleAmericano extends Coffee {}
public class ItalianStyleCappucino extends Coffee {}
public class ItalianStyleCaffeLatte extends Coffee {}
public class ItalianStyleEspresso extends Coffee {}

public class AmericanStyleAmericano extends Coffee {}
public class AmericanStyleCappucino extends Coffee {}
public class AmericanStyleCaffeLatte extends Coffee {}
public class AmericanStyleEspresso extends Coffee {}
由於我們要保留當前的業務模型,因此我們希望該orderCoffee(CoffeeType type)方法進行盡可能少的更改。看看它:

public Coffee orderCoffee(CoffeeType type) {
    Coffee coffee = coffeeFactory.createCoffee(type);
    coffee.grindCoffee();
    coffee.makeCoffee();
    coffee.pourIntoCup();

    System.out.println("Here's your coffee! Thanks! Come again!");
    return coffee;
}
我們有什麼選擇?好吧,我們已經知道如何編寫工廠了,對吧?立即想到的最簡單的事情就是編寫兩個類似的工廠,然後將所需的實現傳遞給我們咖啡店的構造函數。通過這樣做,咖啡店的等級不會改變。首先,我們需要新建一個工廠類,讓它繼承我們的簡單工廠,然後重寫方法createCoffee(CoffeeType type)。讓我們編寫用於創建意式咖啡和美式咖啡的工廠:

public class SimpleItalianCoffeeFactory extends SimpleCoffeeFactory {

    @Override
    public Coffee createCoffee(CoffeeType type) {
        Coffee coffee = null;
        switch (type) {
            case AMERICANO:
                coffee = new ItalianStyleAmericano();
                break;
            case ESPRESSO:
                coffee = new ItalianStyleEspresso();
                break;
            case CAPPUCCINO:
                coffee = new ItalianStyleCappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new ItalianStyleCaffeLatte();
                break;
        }
        return coffee;
    }
}

public class SimpleAmericanCoffeeFactory extends SimpleCoffeeFactory{

    @Override
    public Coffee createCoffee (CoffeeType type) {
        Coffee coffee = null;

        switch (type) {
            case AMERICANO:
                coffee = new AmericanStyleAmericano();
                break;
            case ESPRESSO:
                coffee = new AmericanStyleEspresso();
                break;
            case CAPPUCCINO:
                coffee = new AmericanStyleCappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new AmericanStyleCaffeLatte();
                break;
        }

        return coffee;
    }

}
現在我們可以將所需的工廠實現傳遞給 CoffeeShop。讓我們看看從不同咖啡店訂購咖啡的代碼是什麼樣的。比如意式卡布奇諾和美式卡布奇諾:

public class Main {
    public static void main(String[] args) {
        /*
            Order an Italian-style cappuccino:
            1. Create a factory for making Italian coffee
            2. Create a new coffee shop, passing the Italian coffee factory to it through the constructor
            3. Order our coffee
         */
        SimpleItalianCoffeeFactory italianCoffeeFactory = new SimpleItalianCoffeeFactory();
        CoffeeShop italianCoffeeShop = new CoffeeShop(italianCoffeeFactory);
        italianCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
        
        
         /*
            Order an American-style cappuccino
            1. Create a factory for making American coffee
            2. Create a new coffee shop, passing the American coffee factory to it through the constructor
            3. Order our coffee
         */
        SimpleAmericanCoffeeFactory americanCoffeeFactory = new SimpleAmericanCoffeeFactory();
        CoffeeShop americanCoffeeShop = new CoffeeShop(americanCoffeeFactory);
        americanCoffeeShop.orderCoffee(CoffeeType.CAPPUCCINO);
    }
}
我們創建了兩個不同的咖啡店,將所需的工廠傳遞給每個咖啡店。一方面,我們已經實現了我們的目標,但另一方面……不知何故,這讓企業家們不太滿意……讓我們找出問題所在。一是工廠多。什麼?現在對於每個新位置,我們應該創建自己的工廠,除此之外,確保在創建咖啡店時將相關工廠傳遞給構造函數?第二,它仍然是一個簡單的工廠。只是稍微現代化了。但我們來這裡是為了學習一種新模式。第三,是否可以採用不同的方法?如果我們能把所有與咖啡準備有關的問題都放在CoffeeShop通過將創建咖啡和服務訂單的過程聯繫起來進行分類,同時保持足夠的靈活性來製作各種風格的咖啡。答案是肯定的,我們可以。這稱為工廠方法設計模式。

從簡單工廠到工廠方法

為了盡可能高效地解決任務:
  1. 我們將createCoffee(CoffeeType type)方法返回給CoffeeShop類。
  2. 我們將把這個方法抽象化。
  3. CoffeeShop本身將變得抽象。
  4. CoffeeShop班級將有子班級。
是的,朋友。意大利咖啡店無非是階級的後代CoffeeShop,它createCoffee(CoffeeType type)按照意大利咖啡師的最佳傳統實施方法。現在,一步一個腳印。步驟 1. 使Coffee類抽象。我們有兩個完整的不同產品系列。不過,意大利咖啡和美國咖啡有一個共同的祖先——階級Coffee。將其抽象化是合適的:

public abstract class Coffee {
    public void makeCoffee(){
        // Brew the coffee
    }
    public void pourIntoCup(){
        // Pour into a cup
    }
}
步驟 2. 使用CoffeeShop抽象createCoffee(CoffeeType type)方法 進行抽象

public abstract class CoffeeShop {

    public Coffee orderCoffee(CoffeeType type) {
        Coffee coffee = createCoffee(type);

        coffee.makeCoffee();
        coffee.pourIntoCup();

        System.out.println("Here's your coffee! Thanks! Come again!");
        return coffee;
    }

    protected abstract Coffee createCoffee(CoffeeType type);
}
第 3 步。創建一家意大利咖啡店,它是抽象咖啡店的後代。我們createCoffee(CoffeeType type)在其中實施該方法,同時考慮到意大利食譜的具體情況。

public class ItalianCoffeeShop extends CoffeeShop {

    @Override
    public Coffee createCoffee (CoffeeType type) {
        Coffee coffee = null;
        switch (type) {
            case AMERICANO:
                coffee = new ItalianStyleAmericano();
                break;
            case ESPRESSO:
                coffee = new ItalianStyleEspresso();
                break;
            case CAPPUCCINO:
                coffee = new ItalianStyleCappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new ItalianStyleCaffeLatte();
                break;
        }
        return coffee;
    }
}
Step 4. 美式咖啡店同理

public class AmericanCoffeeShop extends CoffeeShop {
    @Override
    public Coffee createCoffee(CoffeeType type) {
        Coffee coffee = null;

        switch (type) {
            case AMERICANO:
                coffee = new AmericanStyleAmericano();
                break;
            case ESPRESSO:
                coffee = new AmericanStyleEspresso();
                break;
            case CAPPUCCINO:
                coffee = new AmericanStyleCappuccino();
                break;
            case CAFFE_LATTE:
                coffee = new AmericanStyleCaffeLatte();
                break;
        }

        return coffee;
    }
}
第 5 步。查看美式和意式拿鐵咖啡的外觀:

public class Main {
    public static void main(String[] args) {
        CoffeeShop italianCoffeeShop = new ItalianCoffeeShop();
        italianCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);

        CoffeeShop americanCoffeeShop = new AmericanCoffeeShop();
        americanCoffeeShop.orderCoffee(CoffeeType.CAFFE_LATTE);
    }
}
恭喜。我們剛剛以我們的咖啡店為例實現了工廠方法設計模式。

工廠方法背後的原理

現在讓我們更詳細地考慮我們得到了什麼。下圖顯示了生成的類。綠色塊是創作者類,藍色塊是產品類。 設計模式:工廠方法 - 2我們可以得出什麼結論?
  1. 所有產品都是抽像Coffee類的實現。
  2. 所有的創建者都是抽像CoffeeShop類的實現。
  3. 我們看到兩個平行的類層次結構:
    • 產品的層次結構。我們看到意大利後裔和美國後裔
    • 創作者的等級制度。我們看到意大利後裔和美國後裔
  4. CoffeeShop類沒有關於Coffee將創建哪個特定產品 ( ) 的信息。
  5. CoffeeShop類將特定產品的創建委託給其後代。
  6. 該類的每個後代都根據自己的具體功能CoffeeShop實現一個工廠方法。createCoffee()換句話說,生產者類的實現根據生產者類的具體情況準備特定的產品。
您現在已準備好定義工廠方法模式。工廠方法模式定義了創建對象的接口,但允許子類選擇創建對象的類。因此,工廠方法將實例的創建委託給子類。一般來說,記住定義不如理解它是如何工作的重要。

工廠方法的結構

設計模式:工廠方法 - 3上圖顯示了工廠方法模式的一般結構。這裡還有什麼重要的?
  1. Creator 類實現了除工廠方法之外的所有與產品交互的方法。
  2. 抽象factoryMethod()方法必須由該類的所有後代實現Creator
  3. 該類ConcreteCreator實現了factoryMethod()直接創建產品的方法。
  4. 此類負責創建特定產品。這是唯一包含有關創建這些產品的信息的類。
  5. 所有產品都必須實現一個通用接口,即它們必須是一個通用產品類的後代。這是必要的,以便使用產品的類可以作為抽象而不是特定的實現對它們進行操作。

家庭作業

今天我們做了不少功課,學習了工廠方法設計模式。是時候加強材料了!練習 1. 做開另一家咖啡店的工作。它可以是英式或西班牙式咖啡店。甚至是宇宙飛船風格。在咖啡中添加食用色素使其發光,您的咖啡將簡直出類拔萃!練習 2. 在上一課中,您進行了創建虛擬壽司吧或虛擬比薩店的練習。現在你的練習是不要站著不動。今天您學習瞭如何使用工廠方法模式來發揮您的優勢。是時候利用這些知識擴展您自己的業務了;)