一天一个设计模式——抽象工厂模式

283 阅读4分钟

参考:refactoringguru.cn/design-patt…

意图

抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。

问题

假设你正在开发一款UI元素管理应用。该应用包括一些类,用于表示:

  1. 一系列相关产品:如ButtonCheckBox
  2. 系列产品的不同变体:可以使用MacWin等不同风格生成ButtonCheckBox

现在,你要如何确保你生成的产品是同一种风格的?并且,如果要添加新产品或新风格时候,如何确保不改动已有的代码?

解决方案

首先,抽象工厂模式建议为系列中的每件产品明确声明接口 。 然后, 确保所有产品变体都继承这些接口,例如,所有风格的Button都应实现Button接口

接下来, 我们需要声明抽象工厂——包含系列中所有产品构造方法的接口。这些构造方法的返回类型需要声明为产品接口类型。对于每个变体的系列产品, 我们都将基于抽象工厂接口创建不同的工厂类。 每个具体工厂类都只能返回特定风格的产品,例如,Mac­Factory只能创建 MacButtonMacCheckBox等对象。

客户端代码通过相应的抽象接口调用工厂和产品类。 你无需修改实际客户端代码, 就能更改传递给客户端的工厂类, 也能更改客户端代码接收的产品变体。

通过抽象工厂模式,提出对于问题的解决方案,类图如下:

抽象工厂模式示例的类图

抽象工厂模式结构

抽象工厂设计模式

  1. Abstract Product:抽象产品接口,为构成系列产品的一组不同但相关的产品声明接口。如Button接口
  2. Concrete Product:具体产品,对抽象产品接口的不同实现。如所有风格的Button/CheckBox都必须实现Button/CheckBox接口
  3. Abstract Factory:抽象工厂接口,声明了一组创建各种抽象产品的方法
  4. Concrete Factory:具体工厂,实现抽象工厂接口的构建方法。每个具体工厂都对应特定产品变体, 且仅创建此种产品变体。
  5. 客户端 (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;
}

该模式的优缺点

  1. 优点
    • 确保同一工厂生成的产品相互匹配。
    • 可以避免客户端和具体产品代码的耦合。
    • 符合单一职责原则。你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
    • 符合开闭原则。向应用程序中引入新产品变体时, 你无需修改客户端代码。
  2. 缺点
    • 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。