【设计模式】Abstract Factory(抽象工厂)模式

概念

提供一个接口创建以创建一系列相关或相互依赖的对象,而无须指定它们具体的类。

个人理解

1. 适用范围

如果说生成器(Builder)模式是为创建单一复杂对象提供接口并掩饰其内部组成,那么抽象工厂模式的应用场景更适用于多个对象聚为一个分组,而这个分组有不同的系列。
通俗的例子比如,vs界面的白色风格和黑色风格,窗口、滚动条、字体颜色等这些对象实际上除了样式风格不一样,在使用上是完全一致的,不可能它换个颜色我就不能使用它的功能了。
再比如,一个项目我链接了一个数据库,使用它的增删改查的功能,但是它是sql还是mysql还是sqlite实际在客户端那边是无所谓的,只要能用这些功能就行。

  • 一个系统要独立于它的产品的创建、组合和表示。
  • 一个系统要由多个产品系列中的一个来配置。(上面的数据库例子)
  • 要强调一系列相关产品对象的设计以便进行联合使用。
  • 提供一个产品类库,但只想显示它们的接口而不是实现。

需要理解的是,客户端需要的是产品的功能,而产品具体使用什么方式实现这些功能,通过什么样式呈现出同样的功能,客户端是不关心的。这样实际上在客户端这边我们只需要得到一个 抽象的产品 ,它提供客户端需要的所有功能接口。

我们的初衷是让用户根据自己需要获得对应一系列的对象,并不希望他看到对象里面功能的具体实现,因此需要一个类来根据用户所想来分发对应的对象,这个类就称为 工厂 ,同时我们又希望存在多个具体工厂提供不同系列创建的对象。

2. 代码实现(C++)

2.1 思路

有两个产品对象A和B,A有A1和A2两种,B有B1和B2两种,且A1和B1捆绑为一个系列,A2和B2捆绑为一个系列。
那么抽象工厂模式有以下几个模块:

  1. AbstractProductA:抽象产品A的接口,它声明A中需要的所有功能接口,而把具体细节的创建交给子类。
  2. ProductA1、ProductA2:继承于AbstractProductA,用户获得的是一个抽象的产品,因此这两个类中所声明的函数是用户不可见的。(产品B类似)
  3. AbstractFactory:一个抽象的工厂接口,主要声明创建一个系列所需要的所有对象列表、获得这些对象的方法以及创建这些对象的抽象接口。
  4. ConcreteFactory1:继承于AbstractFactory,把对象的创建重写为系列1(即A1和B1)的创建方法。(ConcreteFactory2类似,即系列2)

2.2 产品模块

把产品对象A和B场景化为保存一个存放常量字符串链表的类,A1和A2的区别设置在头节点字符串的不同,产品A存在一个对外的(用户可用)功能函数 addA 以向链表中添加节点,最后提供一个 GetList 函数用于获取链表的头节点。
具体类图如下:
Product UML
抽象产品的构造函数只起到把链表头尾指针初始化的作用,字符串赋值放在了 InitA 中供具体工厂调用来真正创建具体的产品A1/A2。
代码如下:(B类似不再赘述。)

typedef struct ListNode
{
	const char* str;
	ListNode* next;

	ListNode(const char* s)
	{
		str = s;
		next = nullptr;
	}
}ListNode;

class AbstractProductA
{
public:
	AbstractProductA()
	{
		headA = new ListNode("Product A head");
		tailA = headA;
	}
	void addA(const char* s)
	{
		ListNode* p = new ListNode(s);
		tailA->next = p;
		tailA = p;
	}
	ListNode* GetList()
	{
		return headA;
	}
protected:
	ListNode* headA;
	ListNode* tailA;
	virtual void InitA() {};
};

class ProductA1 :public AbstractProductA
{
public:
	void InitA()
	{
		headA->str = "Product A1 head"; //A1、A2的区别体现在此处
	}
};

class ProductA2 :public AbstractProductA
{
public:
	void InitA()
	{
		headA->str = "Product A2 head";
	}
};

2.3 工厂模块

工厂主要是提供创建A、B对象的以及获得它们的接口。
具体结构如下图:
Factory
这样设计,创建A和B是分开的,也就是用户可用自行选择什么时候调用CreateProductA/B函数来创建对应A/B对象。
代码如下:(ConcreteFactory2类似,不再赘述)

class AbstractFactory
{
public:
	virtual void CreateProductA() {}
	virtual void CreateProductB() {}
	AbstractProductA& GetProdctA()
	{
		return productA;
	}
	AbstractProductB& GetProdctB()
	{
		return productB;
	}
protected:
	AbstractProductA productA;
	AbstractProductB productB;
};

class ConcreteFactory1 :public AbstractFactory
{
public:
	void CreateProductA()
	{
		ProductA1 product;
		product.InitA();
		productA = product;
	}
	void CreateProductB()
	{
		ProductB1 product;
		product.InitB();
		productB = product;
	}
};

2.4 用户使用

前面提到过,对于用户来说,抽象的产品已经应有尽有,用户无需知道 A1和A2具体要怎么去区分创建,只需要知道有这么两个具体工厂可以创建它们就可以。而且使用时就算实例的是AbstractFactory引用类型的具体工厂也是可以的。
代码如下:(具体工厂2的使用类似,不再赘述)

	AbstractFactory* factory = new ConcreteFactory1();	//实例工厂
		
	factory->CreateProductA();	//创建产品
	factory->CreateProductB();

	AbstractProductA productA = factory->GetProdctA();	//获取产品
	AbstractProductB productB = factory->GetProdctB();

	productA.addA("factory1");	//使用产品的功能函数
	productB.addB("factory1");

	//可视化看看有没有问题
	ListNode* p = productA.GetList();
	while (p->next != NULL)
	{
		cout << p->str << " -> ";
		p = p->next;
	}
	cout << p->str << endl;
	p = productB.GetList();
	while (p->next != NULL)
	{
		cout << p->str << " -> ";
		p = p->next;
	}
	cout << p->str << endl;

实例工厂处,选择实例 AbstractFactory 的引用类型,目的是之后还要测试一下 ConcreteFactory2 ,到时直接 factory = new ConcreteFactory2(); 就可以不用再新建一个具体工厂对象了。

2.5 运行结果

运行结果如下:
运行结果

3. 优缺点

优点:

  1. 客户与类的实现分离。 从上面是可以看出客户使用的是抽象产品的接口,完全没有与ProductA1有过交互,可以说客户不知道具体的类的名称(不出现在客户代码)。
  2. 易于实现产品系列切换。 这个很好理解,比如甲方爸爸突然说要换一个数据库,当你没有用抽象工厂模式,你发现客户端的代码全是具体类型数据库接口的调用,一改要改客户代码N多处,头都大了;而当你用了抽象工厂模式,你就可以丝毫不慌,在开始时换一个具体工厂的实例就可以完美切换。
  3. 有利于产品的一致性。 当一个系统一次只能使用一个系列的产品,那么抽象工厂模式就很容易保证这一点。

缺点:

  • 难以扩展新种类。 这个体现在两个方面:
  1. 为已有产品添加 新的系列:这样的话需要在各个抽象产品类下新建一个子类,然后在抽象工厂下新建一个具体工厂子类,假设目前存在n个产品,那么一下就要新写n+1个类,这个工作量可以接受但也不小(当然新的系列也完全可以是使用现有的具体产品来排列组合,比如上面的例子我把A1和B2捆绑为一个新的系列命名为ConcreteFactory3,此时工作量就不大了)。
  2. 在产品列表添加 新的产品种类:这个可以说是灾难,首先要为这个对象新建一个抽象产品类,并为每个系列在其下新建一个具体产品类;其次抽象工厂类中要声明这个对象、它的创建方法以及它的获取方法;最后要在各个具体工厂类中实现它的创建方法。假设目前存在n个系列,那么就要新写/修改2n+2个类,工作量巨大还难改全。

由此看来,抽象工厂模式还是更适合一个系列中的产品种类基本不扩展的情况。

但但但是,也是有方法可以定义可扩展的工厂。当然它是有代价的,经典用灵活性换安全性。

4. 可扩展的工厂(简单工厂)

为创建对象的操作添加一个参数,可以是enum的种类,可以是字符串,它为创建对象的操作提供了需要创建的对象的种类。要让这么做有效(创建的对象能够存放在一个可访问的地址,或是直接将创建操作的返回类型设置为对象对应的类型),那么首先所有的对象就需要继承在一个基类下。如果想要用户使用对象具体的功能接口,那么就需要对这个对象做一个向下类型转换。用上面的场景来实现的话,如下代码:

enum PRODUCT_TYPE
{
	A1,
	A2,
	B1,
	B2
};

class AbstractProduct
//此类用于让所有抽象产品继承这个类
//即:class AbstractProductA :public AbstractProduct
{
public:
	virtual void test() {}
	//此函数无实际用处,由于C++的dynamic_cast需要多态的参数,这个类必须存在虚函数才加的
};

class Factory
{
public:
	AbstractProduct* CreateProduct(PRODUCT_TYPE PT)
	{
		switch (PT)
		{
		case A1:
		{
			ProductA1 p;
			p.InitA();
			product = &p;
			return product;
			break;
		}
		case A2:
		{
			ProductA2 p;
			p.InitA();
			product = &p;
			return product;
			break;
		}
		case B1:
		{
			ProductB1 p;
			p.InitB();
			product = &p;
			return product;
			break;
		}
		case B2:
		{
			ProductB2 p;
			p.InitB();
			product = &p;
			return product;
			break;
		}
		default:
			return product;
			break;
		}
	}
private:
	AbstractProduct* product;
};

使用的代码如下:

Factory factory;
AbstractProduct* pro = factory.CreateProduct(A1);
AbstractProductA productA = *dynamic_cast<AbstractProductA*>(pro);

productA.addA("factory");
ListNode* p = productA.GetList();
while (p->next != NULL)
{
	cout << p->str << " -> ";
	p = p->next;
}
cout << p->str << endl;

运行结果也不无不同:
结果

上面的代码表示,创建A和B,由 PRODUCT_TYPE 指定,用户想要哪个提供参数就行,而使用过程中,需要用到C++的动态强制转换 dynamic_cast ,但是这个动态强制转换并不能总是生效,向下对 AbstractProduct 转换成 AbstractProductA 可能会失败,不能保证安全性。

除了不安全之外,同时产品的名字也或多或少地展现给了用户(当然你也可能自定义字符串与类名做区分)。最重要的一点是,它把所有产品都糅合在一个工厂里,而抽象工厂模式将对象以系列区分,一个应用在使用时不能创建其他系列的对象。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值