创建型设计模式(4)—— 抽象工厂模式(Abstract Factory Pattern)

2024-05-24 14:31:50 浏览数 (2)

1.概述

使用设计模式可以提高代码的可复用性、可扩充性和可维护性。

抽象工厂模式(Abstract Factory Pattern)属于创建型模式,为创建一组相关或者相互依赖的对象(产品族)提供一个抽象类接口,而无需指定它们的具体类。 产品族的定义是:某个具体工厂生产所有类型的产品,比如定义了一个抽象工厂接口 A,它可以生产三种产品:p1、p2、p3,而这三个产品就叫一个产品族。

抽象工厂模式可以说是工厂方法模式的升级版,当需要创建的产品有多个产品族时使用抽象工厂模式是比较好的选择。一个产品由多个产品族构成,那什么是产品族呢?

还是拿我们在简单工厂模式和工厂方法模式中生产比萨来举例。我们一直都是在一个比萨店生产三种不同口味的比萨,但是不同地方的比萨店生产的同一品种的比萨点使用的原料可能存在差异。比如纽约和芝加哥的比萨店都在生产蛤蜊比萨,但是纽约喜欢薄的且靠近大海,所以使用较小的面团和新鲜的蛤蜊,芝加哥使用较大的面团和冷冻的蛤蜊。某种原材料的不同类型构成一个产品族,比如小面团和大面团是一个产品族,新鲜蛤蜊和冷冻蛤蜊是一个产品族。面团产品族和蛤蜊产品族构成一个披萨产品。

如果使用工厂方法模式来生产原料已经满足不了需求,因为生产对象的总类太多,这一篇文章我们将用抽象工厂模式来解决这一问题。

抽象工厂模式类图:

在抽象工厂模式中有如下角色: AbstractFactory:抽象工厂类,它声明了用来创建不同产品的方法。 ConcreteFactory:具体工厂类,实现抽象工厂中申明的创建产品的方法。 AbstractProduct:抽象产品类,为每种产品声明抽象描述方法。比如上图的 AbstractProductA 和 AbstractProductB。 ConcreteProduct:具体产品类,定义具体工厂生产的具体产品,并实现抽象产品类中申明的抽象描述方法。

2.示例

下面以 C 为例,首先定义抽象产品类,分别为面团 Dough 和蛤蜊 Clam:

代码语言:javascript复制
//面团
class Dough{
public:
	virtual string getDescription() = 0;
};

//蛤蜊
class Clam{
public:
	virtual string getDescription() = 0;
};

现在实现纽约和芝加哥两地比萨店使用不同的具体原料类:

代码语言:javascript复制
//纽约面团
class NewYorkDough:public Dough{
public:
	virtual string getDescription() {
		return "纽约小面团";
	};
};
//纽约蛤蜊
class NewYorkClam:public Clam{
public:
	virtual string getDescription() {
		return "纽约新鲜蛤蜊";
	};
};

//芝加哥面团
class ChicagoDough:public Dough{
public:
	virtual string getDescription() {
		return "芝加哥厚面团";
	}
};
//芝加哥蛤蜊
class ChicagoClam:public Clam{
public:
	virtual string getDescription() {
		return "芝加哥冷冻蛤蜊";
	}
};

有了抽象产品类和具体产品类,现在来完成抽象工厂和具体工厂的设计和实现。

代码语言:javascript复制
//抽象工厂
class AbstractFactory {
public:
	virtual Dough* createDough() = 0;
	virtual Clam* createClam() = 0;
};

//具体生产纽约比萨原料工厂
class NewYorkFactory :public AbstractFactory {
public:
	Dough* createDough() {
		return new NewYorkDough;
	}
	Clam* createClam() {
		return new NewYorkClam;
	}
};
//具体生产芝加哥比萨原料工厂
class ChicagoFactory :public AbstractFactory {
public:
	Dough* createDough() {
		return new ChicagoDough;
	}
	Clam* createClam() {
		return new ChicagoClam;
	}
};

客户端代码,使用工厂来生产具体的比萨原料。

代码语言:javascript复制
#include <iostream>  
#include <string>
using namespace std;

int main() {
	AbstractFactory* af1 = new NewYorkFactory;
	Dough* dough1 = af1->createDough();	//生产纽约薄面团
	cout << dough1->getDescription() << endl;
	Clam* clam1 = af1->createClam();	//生产纽约新鲜蛤蜊
	cout << clam1->getDescription() << endl;

	AbstractFactory* af2 = new ChicagoFactory;
	Dough* dough2 = af2->createDough();	//生产芝加哥厚面团
	cout << dough2->getDescription() << endl;
	Clam* clam2 = af2->createClam();	//生产芝加哥冷冻蛤蜊
	cout << clam2->getDescription() << endl;
	system("pause");
}

程序运行结果:

代码语言:javascript复制
纽约薄面团
纽约新鲜蛤蜊
芝加哥厚面团
芝加哥冷冻蛤蜊

3.应用场景和优缺点

应用场景: (1)一个系统不依赖于产品族实例如何被创建、组合和表达的细节。如本文例子中一个比萨店生产的比萨原料是一个产品族。 (2)系统中有多个产品族,存在着多个抽象类,如蛤蜊基类、面团基类等。并且产品族之间存在着一定的关联或者约束,就可以使用抽象工厂模式。假如各个抽象类的实现类之间不存在关联或约束,则使用多个独立的工厂来创建产品,则更合适一点,比如我们在工厂方法模式中使用工厂方法模式来创建不同口味的比萨。

优点: 抽象工厂模式除了具有工厂方法模式的优点外,最主要的优点就是可以在类的内部对产品族进行约束。所谓的产品族,一般或多或少的都存在一定的关联,抽象工厂模式就可以在类内部对产品族的关联关系进行定义和描述,而不必专门引入一个新的类来进行管理。

缺点: 产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则需要修改抽象工厂类和生产该产品族的具体工厂类。所以使用抽象工厂模式时,对产品族的划分是非常重要的。

4.与简单工厂和工厂方法模式的对比

简单工厂模式,一个工厂类根据入参的不同生产不同类型的产品。

工厂方法模式,一个具体工厂类生产一个具体类型的产品。不同具体类型的产品由不同具体工厂类生产。

抽象工厂模式,一个具体工厂类生产不同产品族的具体产品。不同产品族的产品组合由不同具体工厂类生产。

无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也极为相似,他们的最终目的都是为了解耦。

在使用时,我们不必去在意这个模式到底是哪种工厂模式,因为他们之间的演变常常令人琢磨不透。经常你会发现,明明使用的是工厂方法模式,当新需求来临,稍加修改,在抽象工厂类中加入一个新的生产产品方法后,由于抽象工厂类中的产品构成了不同抽象基类中具体产品,具体工厂类生产的产品组成了一个产品族,它就变成抽象工厂模式了。而对于抽象工厂模式,当减少一个方法使提供的产品不再构成产品族之后,它就演变成了工厂方法模式。

所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。

参考文献

23种设计模式(3):抽象工厂模式 设计模式(十三)抽象工厂模式

0 人点赞