意图
抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。
问题
假设你正在开发一款UI元素管理应用。该应用包括一些类,用于表示:
- 一系列相关产品:如
Button
,CheckBox
等 - 系列产品的不同变体:可以使用
Mac
,Win
等不同风格生成Button
,CheckBox
现在,你要如何确保你生成的产品是同一种风格的?并且,如果要添加新产品或新风格时候,如何确保不改动已有的代码?
解决方案
首先,抽象工厂模式建议为系列中的每件产品明确声明接口 。 然后, 确保所有产品变体都继承这些接口,例如,所有风格的Button都应实现Button接口。
接下来, 我们需要声明抽象工厂——包含系列中所有产品构造方法的接口。这些构造方法的返回类型需要声明为产品接口类型。对于每个变体的系列产品, 我们都将基于抽象工厂接口创建不同的工厂类。 每个具体工厂类都只能返回特定风格的产品,例如,MacFactory
只能创建 MacButton
、 MacCheckBox
等对象。
客户端代码通过相应的抽象接口调用工厂和产品类。 你无需修改实际客户端代码, 就能更改传递给客户端的工厂类, 也能更改客户端代码接收的产品变体。
通过抽象工厂模式,提出对于问题的解决方案,类图如下:
抽象工厂模式结构
- Abstract Product:抽象产品接口,为构成系列产品的一组不同但相关的产品声明接口。如Button接口
- Concrete Product:具体产品,对抽象产品接口的不同实现。如所有风格的Button/CheckBox都必须实现Button/CheckBox接口
- Abstract Factory:抽象工厂接口,声明了一组创建各种抽象产品的方法
- Concrete Factory:具体工厂,实现抽象工厂接口的构建方法。每个具体工厂都对应特定产品变体, 且仅创建此种产品变体。
- 客户端 (Client) 只需通过抽象接口调用工厂和产品对象, 就能与任何具体工厂/产品变体交互。因为客户端仅接触抽象接口, 那么谁来创建实际的工厂对象呢? 一般情况下, 应用程序会在初始化阶段创建具体工厂对象。
对问题的代码实现
#include<iostream>
#include<string>
using namespace std;
// 系列产品中的特定产品必须有一个基础接口。所有产品变体都必须实现这个接口。
// 抽象产品接口:Button
class Button {
public:
~Button()
{
}
virtual void render() const = 0;
};
//不同风格的具体产品,对接口有不同的实现
class WinButton :public Button {
public:
virtual void render() const {
//根据Win渲染按钮
cout << "按钮:我是Win风格的按钮" << endl;
}
};
class MacButton :public Button {
public:
virtual void render() const {
//根据Win渲染按钮
cout << "按钮:我是Mac风格的按钮" << endl;
}
};
// 抽象产品接口:CheckBox
class CheckBox {
public:
~CheckBox()
{
}
virtual void render() const = 0;
//不同的产品之间可以交互,但要确定他们是处于同一变体下
virtual void collaborate(const Button *p_but) const = 0;
};
class WinCheckBox :public CheckBox {
public:
virtual void render() const {
cout << "复选框:我是Win风格的复选框" << endl;
}
virtual void collaborate(const Button *p_but) const {
cout << "复选框:我要和WinButton合作--";
p_but->render();
}
};
class MacCheckBox :public CheckBox {
public:
virtual void render() const {
cout << "复选框:我是Mac风格的复选框" << endl;
}
virtual void collaborate(const Button *p_but) const {
cout << "复选框:我要和MacButton合作--";
p_but->render();
}
};
// 抽象工厂接口声明了一组能返回不同抽象产品的方法。这些产品属于同一个系列
// 且在高层主题或概念上具有相关性。同系列的产品通常能相互搭配使用。系列产
// 品可有多个变体,但不同变体的产品不能搭配使用。
class GUIFactory {
public:
~GUIFactory()
{
}
virtual Button* create_button() const = 0;
virtual CheckBox* create_checkbox() const = 0;
};
// 具体工厂可生成属于同一变体的系列产品。工厂会确保其创建的产品能相互搭配
// 使用。具体工厂方法签名会返回一个抽象产品,但在方法内部则会对具体产品进
// 行实例化。
class WinFactory :public GUIFactory {
public:
virtual Button* create_button() const {
return new WinButton();
}
virtual CheckBox* create_checkbox() const {
return new WinCheckBox();
}
};
class MacFactory :public GUIFactory {
public:
virtual Button* create_button() const {
return new MacButton();
}
virtual CheckBox* create_checkbox() const {
return new MacCheckBox();
}
};
// 客户端代码仅通过抽象类型(GUIFactory、Button 和 Checkbox)使用工厂
// 和产品。这让你无需修改任何工厂或产品子类就能将其传递给客户端代码。
void client_code(GUIFactory* p_fac) {
const Button *p_but = p_fac->create_button();
const CheckBox *p_che = p_fac->create_checkbox();
p_but->render();
p_che->render();
//使用抽象工厂模式,可以保证生成的产品处于同一种变体(风格)
p_che->collaborate(p_but);
delete p_but;
delete p_che;
}
// 客户端仅接触抽象接口, 那么谁来创建实际的工厂对象呢? 一般情况下,
// 应用程序会在初始化阶段创建具体工厂对象。
// 程序会根据当前配置或环境设定选择工厂类型,并在运行时创建工厂(通常在初
// 始化阶段)。
int main() {
cout << "APP:先使用Win风格的UI元素" << endl;
WinFactory *p_win_fac = new WinFactory();
client_code(p_win_fac);
delete p_win_fac;
cout << endl;
cout << "APP:使用Mac风格的UI元素" << endl;
MacFactory *p_mac_fac = new MacFactory();
client_code(p_mac_fac);
delete p_mac_fac;
cout << endl;
}
该模式的优缺点
- 优点
- 确保同一工厂生成的产品相互匹配。
- 可以避免客户端和具体产品代码的耦合。
- 符合单一职责原则。你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
- 符合开闭原则。向应用程序中引入新产品变体时, 你无需修改客户端代码。
- 缺点
- 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。