你好!今天我們繼續研究設計模式,我們將討論抽象工廠模式。 設計模式:抽象工廠 - 1以下是我們將在課程中介紹的內容:
  • 我們將討論什麼是抽象工廠以及這種模式解決了什麼問題
  • 我們將創建跨平台應用程序的框架,用於通過用戶界面訂購咖啡
  • 我們將研究如何使用此模式的說明,包括查看圖表和代碼
  • 作為獎勵,本課程包含一個隱藏的彩蛋,它將幫助您學習如何使用 Java 確定操作系統的名稱,並根據結果執行一個或另一個操作。
要完全理解此模式,您需要精通以下主題:
  • Java中的繼承
  • Java 中的抽像類和方法

抽象工廠解決什麼問題?

像所有工廠模式一樣,抽象工廠幫助我們確保正確創建新對象。我們用它來管理各種互連對象系列的“生產”。各種相互關聯的對象系列......這是什麼意思?別擔心:在實踐中,一切都比看起來簡單。首先,相互關聯的對象家族可能是什麼?假設我們正在製定一項涉及多種類型單位的軍事戰略:
  • 步兵
  • 騎兵
  • 弓箭手
這些類型的單位是相互關聯的,因為它們在同一支軍隊中服役。我們可以說上面列出的類別是一系列相互關聯的對象。我們明白這一點。但是抽象工廠模式用於安排創建各種相互關聯的對象系列。這裡也沒有什麼複雜的。讓我們繼續以軍事戰略為例。一般來說,軍事單位屬於幾個不同的交戰方。根據站在哪一邊,軍事單位的外觀可能會有很大差異。羅馬軍隊的步兵、騎兵和弓箭手與維京步兵、騎兵和弓箭手不同。在軍事戰略中,不同軍隊的士兵是相互關聯的對象的不同家族。如果一個程序員'會很有趣' 這個錯誤導致一名身穿拿破崙時代法國製服、手持火槍的士兵走在羅馬步兵的隊伍中。抽象工廠設計模式正是解決這個問題所需要的。不,不是時間旅行帶來的尷尬問題,而是創建各種相互關聯的對象組的問題。抽象工廠提供用於創建所有可用產品(對象族)的接口。一個抽象工廠通常有多個實現。他們每個人都負責創建一個家庭的產品。我們的軍事戰略將包括一個創建抽象步兵、弓箭手和騎兵的抽象工廠,以及該工廠的實現。例如,一家生產羅馬軍團士兵的工廠和一家生產迦太基士兵的工廠。抽像是該模式最重要的指導原則。工廠的客戶僅通過抽象接口與工廠及其產品一起工作。因此,您不必考慮當前正在創建哪些士兵。相反,您將此職責傳遞給​​抽象工廠的一些具體實現。

讓我們繼續自動化我們的咖啡店

上一課,我們研究了工廠方法模式。我們用它來擴展我們的咖啡業務並開設了幾個新地點。今天,我們將繼續實現業務現代化。使用抽象工廠模式,我們將為在線訂購咖啡的新桌面應用程序奠定基礎。在編寫桌面應用程序時,我們應該始終考慮跨平台支持。我們的應用程序必須同時在 macOS 和 Windows 上運行(劇透:對 Linux 的支持留給您作為家庭作業來實現)。我們的應用程序會是什麼樣子?非常簡單:它將是一個由文本字段、選擇字段和按鈕組成的表單。如果您有使用不同操作系統的經驗,您肯定會注意到 Windows 上的按鈕呈現方式與 Mac 上的不同。和其他一切一樣……好吧,讓我們開始吧。
  • 鈕扣
  • 文本域
  • 選擇字段
免責聲明:在每個接口中,我們可以定義諸如onClickonValueChanged或 之類的方法onInputChanged。換句話說,我們可以定義允許我們處理各種事件的方法(按下按鈕、輸入文本、在選擇框中選擇一個值)。這里特意省略了所有這些,以免示例過載並在我們研究工廠模式時使其更加清晰。讓我們為我們的產品定義抽象接口:

public interface Button {}
public interface Select {}
public interface TextField {}
對於每個操作系統,我們必須創建操作系統風格的界面元素。我們將為 Windows 和 MacOS 編寫代碼。讓我們為 Windows 創建實現:

public class WindowsButton implements Button {
}

public class WindowsSelect implements Select {
}

public class WindowsTextField implements TextField {
}
現在我們對 MacOS 做同樣的事情:

public class MacButton implements Button {
}

public class MacSelect implements Select {
}

public class MacTextField implements TextField {
}
出色的。現在我們可以繼續我們的抽象工廠,它將創建所有可用的抽象產品類型:

public interface GUIFactory {

    Button createButton();
    TextField createTextField();
    Select createSelect();

}
高超。如您所見,我們還沒有做任何復雜的事情。接下來的一切也很簡單。通過類比產品,我們為每個操作系統創建各種工廠實現。讓我們從 Windows 開始:

public class WindowsGUIFactory implements GUIFactory {
    public WindowsGUIFactory() {
        System.out.println("Creating GUIFactory for Windows OS");
    }

    public Button createButton() {
        System.out.println("Creating Button for Windows OS");
        return new WindowsButton();
    }

    public TextField createTextField() {
        System.out.println("Creating TextField for Windows OS");
        return new WindowsTextField();
    }

    public Select createSelect() {
        System.out.println("Creating Select for Windows OS");
        return new WindowsSelect();
    }
}
我們在方法和構造函數中添加了一些控制台輸出,以進一步說明正在發生的事情。現在對於 macOS:

public class MacGUIFactory implements GUIFactory {
    public MacGUIFactory() {
        System.out.println("Creating GUIFactory for macOS");
    }

    @Override
    public Button createButton() {
        System.out.println("Creating Button for macOS");
        return new MacButton();
    }

    @Override
    public TextField createTextField() {
        System.out.println("Creating TextField for macOS");
        return new MacTextField();
    }

    @Override
    public Select createSelect() {
        System.out.println("Creating Select for macOS");
        return new MacSelect();
    }
}
請注意,每個方法簽名都表明該方法返回一個抽像類型。但在方法內部,我們正在創建產品的特定實現。這是我們控制特定實例創建的唯一地方。現在是時候為表單編寫一個類了。這是一個 Java 類,其字段是接口元素:

public class CoffeeOrderForm {
    private final TextField customerNameTextField;
    private final Select coffeeTypeSelect;
    private final Button orderButton;

    public CoffeeOrderForm(GUIFactory factory) {
        System.out.println("Creating coffee order form");
        customerNameTextField = factory.createTextField();
        coffeeTypeSelect = factory.createSelect();
        orderButton = factory.createButton();
    }
}
創建界面元素的抽象工廠被傳遞給表單的構造函數。我們會將必要的工廠實現傳遞給構造函數,以便為特定操作系統創建界面元素。

public class Application {
    private CoffeeOrderForm coffeeOrderForm;

    public void drawCoffeeOrderForm() {
        // Determine the name of the operating system through System.getProperty()
        String osName = System.getProperty("os.name").toLowerCase();
        GUIFactory guiFactory;

        if (osName.startsWith("win")) { // For Windows
            guiFactory = new WindowsGUIFactory();
        } else if (osName.startsWith("mac")) { // For Mac
            guiFactory = new MacGUIFactory();
        } else {
            System.out.println("Unknown OS. Unable to draw form :(");
            return;
        }
        coffeeOrderForm = new CoffeeOrderForm(guiFactory);
    }

    public static void main(String[] args) {
        Application application = new Application();
        application.drawCoffeeOrderForm();
    }
}
如果我們在 Windows 上運行該應用程序,我們會得到以下輸出:

Creating GUIFactory for Windows OS
Creating coffee order form
Creating TextField for Windows OS
Creating Select for Windows OS
Creating Button for Windows OS
在 Mac 上,輸出將如下所示:

Creating GUIFactory for macOS
Creating coffee order form
Creating TextField for macOS
Creating Select for macOS
Creating Button for macOS
在 Linux 上:

Unknown OS. Unable to draw form :( 
現在我們總結一下。我們編寫了一個基於 GUI 的應用程序的框架,其中的界面元素是專門為相關操作系統創建的。我們將簡潔地重複我們創建的內容:
  • 由輸入字段、選擇字段和按鈕組成的產品系列。
  • 適用於 Windows 和 macOS 的產品系列的不同實現。
  • 定義用於創建我們產品的接口的抽象工廠。
  • 我們工廠的兩個實現,每個負責創建一個特定的產品系列。
  • 一種表單(Java 類),其字段是抽象接口元素,使用抽象工廠在構造函數中使用必要的值進行初始化。
  • 應用程序類 在這個類中,我們創建一個表單,將所需的工廠實現傳遞給它的構造函數。
結果是我們實現了抽象工廠模式。

抽象工廠:如何使用

抽象工廠是一種設計模式,用於管理各種產品系列的創建,而無需綁定到具體的產品類。使用此模式時,您必須:
  1. 定義產品系列。假設我們有兩個:
    • SpecificProductA1,SpecificProductB1
    • SpecificProductA2,SpecificProductB2
  2. 對於系列中的每個產品,定義一個抽像類(接口)。在我們的例子中,我們有:
    • ProductA
    • ProductB
  3. 在每個產品系列中,每個產品都必須實現步驟 2 中定義的接口。
  4. 創建一個抽象工廠,其中包含用於創建步驟 2 中定義的每個產品的方法。在我們的例子中,這些方法將是:
    • ProductA createProductA();
    • ProductB createProductB();
  5. 創建抽象工廠實現,以便每個實現控制單個系列產品的創建。為此,在抽象工廠的每個實現中,您需要實現所有創建方法,以便它們創建並返回特定的產品實現。
下面的 UML 圖說明了上面概述的指令: 設計模式:抽象工廠 - 3現在我們將根據這些指令編寫代碼:

    // Define common product interfaces
    public interface ProductA {}
    public interface ProductB {}

    // Create various implementations (families) of our products
    public class SpecificProductA1 implements ProductA {}
    public class SpecificProductB1 implements ProductB {}

    public class SpecificProductA2 implements ProductA {}
    public class SpecificProductB2 implements ProductB {}

    // Create an abstract factory
    public interface AbstractFactory {
        ProductA createProductA();
        ProductB createProductB();
    }

    // Implement the abstract factory in order to create products in family 1
    public class SpecificFactory1 implements AbstractFactory {

        @Override
        public ProductA createProductA() {
            return new SpecificProductA1();
        }

        @Override
        public ProductB createProductB() {
            return new SpecificProductB1();
        }
    }

    // Implement the abstract factory in order to create products in family 2
    public class SpecificFactory2 implements AbstractFactory {

        @Override
        public ProductA createProductA() {
            return new SpecificProductA2();
        }

        @Override
        public ProductB createProductB() {
            return new SpecificProductB2();
        }
    }

家庭作業

要加固材料,您可以做兩件事:
  1. 改進咖啡訂購應用程序,使其也可以在 Linux 上運行。
  2. 創建您自己的抽象工廠,用於生產參與任何軍事戰略的單位。這可以是涉及真實軍隊的歷史軍事戰略,也可以是涉及獸人、侏儒和精靈的幻想戰略。重要的是選擇你感興趣的東西。發揮創意,在控制台上打印消息,並享受學習模式的樂趣!