概述
前一篇文章中提到的工厂方法模式允许子类决定具体要创建的对象类型,但它一次只创建一个对象。抽象工厂模式则更加复杂,它关注的是创建一系列相关的对象。这些对象通常构成了一个完整的“家族”,并且在不同的实现中保持一致性和兼容性。
跨平台的图形用户界面库(GUI,比如:QT、wxWidgets)是运用抽象工厂模式的一个典型例子:这些库需要为不同的操作系统(比如:Windows、macOS、Linux等)提供一致的控件集,包括按钮、文本框、菜单、多选框等。通过抽象工厂模式,我们可以编写一次代码,而无需关心底层的操作系统差异,只需选择合适的工厂即可创建正确的控件集。
基本原理
抽象工厂模式的基本原理是:提供一种创建一系列相关或依赖对象的接口,而无需指定它们具体的类。这种模式强调的是一个“家族”或一组具有相关性的对象,而不是单一的对象。通过抽象工厂模式,客户端代码可以与这些对象的接口进行交互,而不必关心具体的产品是如何创建和组合在一起的。抽象工厂模式包括如下五个核心组件。
1、抽象工厂。声明了一组用于创建一系列相关产品的方法,这些方法返回的是抽象产品类型,而非具体的产品实例。
2、具体工厂。实现了抽象工厂中定义的方法,以创建具体的产品对象。每个具体工厂,通常对应于一组特定的产品变体。
3、抽象产品。为每种类型的产品定义了一个接口,所有具体产品都必须实现这个接口。
4、具体产品。实现了抽象产品接口的具体类,代表了实际的对象。
5、客户端。只依赖于抽象工厂和抽象产品接口,而不是具体的工厂或产品类。这样,可以确保客户端代码与具体实现解耦。
基于上面的核心组件,抽象工厂模式的实现主要有以下六个步骤。
1、定义抽象产品接口。首先,定义一组抽象产品接口。每个接口代表一类产品,并声明了该类产品必须提供的方法和行为。
2、创建具体产品类。然后,为每种类型的产品创建具体的产品类,确保它们实现了第一步中定义的抽象产品接口,每个具体产品类代表一个特定的产品实现。
3、定义抽象工厂接口。定义一个抽象工厂接口,它声明了一组用于创建各种产品的工厂方法。这些方法返回的是第一步中定义的抽象产品类型,而不是具体的产品实例。
4、实现具体工厂类。为每个产品系列或主题创建具体工厂类,这些类实现了第三步中定义的抽象工厂接口,每个具体工厂负责创建属于同一家族的一系列产品。
5、编写客户端代码。最后,在客户端代码中只依赖于抽象工厂和抽象产品接口进行编程。通过选择合适的具体工厂,客户端可以创建一系列相互关联的对象,而无需知道具体的实现细节。这种方式保证了系统的灵活性和可扩展性,同时保持了不同对象之间的一致性和兼容性。
6、扩展系统。当需要添加新的产品系列时,只需引入新的具体工厂和相应的产品类即可。这种方法不仅简化了对象创建的过程,还提高了代码的维护性和适应变化的能力。
实战解析
在下面的实战代码中,我们使用抽象工厂模式创建了Windows和macOS操作系统下的GUI库。
首先,我们声明了CButton和CTextBox这两个抽象产品接口。它们声明了所有具体产品必须实现的Paint函数,这确保了所有具体产品的行为一致性。
CWinButton、CWinTextBox、CMacButton和CMacTextBox是具体的产品类,分别实现了CButton和CTextBox接口中的Paint方法,这些类代表了特定于Windows和macOS的GUI控件。
接下来,我们声明了抽象工厂类CGUIFactory。它声明了创建按钮和文本框的方法,但没有提供具体的实现。这使得客户端代码可以与任何实现了这个接口的具体工厂一起工作,而不关心具体的产品是如何创建的。
CWinFactory和CMacFactory是具体工厂类,它们实现了CGUIFactory接口中定义的创建方法,分别负责创建Windows风格和macOS风格的控件。
最后,在main函数中,我们创建了一个CWinFactory实例,并调用了CreateGUI函数来创建和使用Windows风格的控件。紧接着,同样的过程重复了一次,这次使用的是CMacFactory来创建和使用macOS风格的控件。
#include <iostream>using namespace std;// 抽象产品:按钮基类
class CButton
{
public:virtual ~CButton() {}virtual void Paint() const = 0;
};// 抽象产品:文本框基类
class CTextBox
{
public:virtual ~CTextBox() {}virtual void Paint() const = 0;
};// Windows具体产品:按钮
class CWinButton : public CButton
{
public:void Paint() const {cout << "Paint a Windows Button" << endl;}
};// Windows具体产品:文本框
class CWinTextBox : public CTextBox
{
public:void Paint() const{cout << "Paint a Windows TextBox" << endl;}
};// macOS具体产品:按钮
class CMacButton : public CButton
{
public:void Paint() const{cout << "Paint a macOS Button" << endl;}
};// macOS具体产品:文本框
class CMacTextBox : public CTextBox
{
public:void Paint() const{cout << "Paint a macOS TextBox" << endl;}
};// 抽象工厂接口
class CGUIFactory
{
public:virtual ~CGUIFactory() {}virtual CButton* CreateButton() const = 0;virtual CTextBox* CreateTextBox() const = 0;
};// Windows具体工厂
class CWinFactory : public CGUIFactory
{
public:CButton* CreateButton() const{return new CWinButton();}CTextBox* CreateTextBox() const{return new CWinTextBox();}
};// macOS具体工厂
class CMacFactory : public CGUIFactory
{
public:CButton* CreateButton() const{return new CMacButton();}CTextBox* CreateTextBox() const{return new CMacTextBox();}
};// 创建特定操作系统的具体产品
void CreateGUI(CGUIFactory& factory)
{CButton* pBtn = factory.CreateButton();pBtn->Paint();delete pBtn;CTextBox* pTextBox = factory.CreateTextBox();pTextBox->Paint();delete pTextBox;
}int main()
{// 客户端代码cout << "Using Windows GUI:" << endl;CWinFactory winFactory;CreateGUI(winFactory);cout << "Using macOS GUI:" << endl;CMacFactory macFactory;CreateGUI(macFactory);return 0;
}
总结
抽象工厂模式可有效降低耦合度,客户端代码只需要处理抽象接口,避免了对具体类的直接引用。还可以在不修改现有代码的情况下添加新产品系列,只需创建新的具体工厂和相应的产品类即可。通过抽象工厂模式,确保了一组相关的产品一起使用,并且可以根据需要轻松切换整个产品系列。
凡事有利亦有弊,抽象工厂模式也在无形中增加了系统的复杂性。由于引入了额外的接口和类,可能会使系统变得更加复杂。另外,抽象工厂模式难以支持新种类的产品。如果需要添加一个新的产品种类,则所有的工厂类都需要进行相应的修改。