经过一层一层抽象,来到了最复杂的抽象工厂模式。
意图
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
UML图
来,直接上 UML 图,结合着它的 UML
图解释:
《大话设计模式》里关于 抽象工厂模式
的图画的内容乍一看很乱,不要慌,我用语言在整理一下:
AbstractFactory
抽象的工厂,声明一个创建抽象产品对象的操作接口ConcreteFactory
具体的工厂,实现创建具体产品对象的操作AbstractProduct
为一类产品对象声明一个接口ConcreteProduct
定义一个将被对应的具体工厂创建的产品对象,实现 AbstractProduct 接口Client
仅使用由AbstractFactory
和AbstractProduct
类声明的接口
通常在使用这个模式的时候, client
在运行时创建一个 ConcreteFactory
类的实例, 这一个具体的工厂创建具有特定实现的产品对象,为创建不同的产品对象,客户应使用不同的具体工厂,大致就是这样
// Client 部分使用代码
AbstractFactory *factory = new ConcreteFactory1();
AbstractProductA *a = factory->CreateProductA();
AbstractProductB *b = factory->CreateProductB();
应用场景
所以使用场景可以分为以下几点:
- 一个系统要独立于它的产品的创建、组合、和表示时
- 一个系统要有多个产品系列中的一个来配置时
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时
- 当你提供一个产品类库时,而只想显示它们的接口而不是实现时。
来个例子
看个例子,可能对应用场景有更深的理解:
我现在产品的基类分别是 Label
LineEdit
PushButton
, 抽象工厂是 AbstractFactory
, 抽象工厂定义了创建产品的操作接口
class AbstractFactory
{
public:
virtual ~AbstractFactory(){}
virtual Label * createLable() = 0;
virtual LineEdit * createLineEdit() = 0;
virtual PushButton * createPushButton() = 0;
}
我先是实现一组 Q
风格的, 我在基类前加个 Q
区分一下
现在3个控件分别是 QLabel
QLineEdit
QPushButton
,都是使用 QConcreteFactory
的工厂来创建的实例。
class QConcreteFactory : public AbstractFactory
{
public:
QConcreteFactory(){}
virtual Label * createLable(){ return new QLabel; }
virtual LineEdit * createLineEdit() { return new QLineEdit; }
virtual PushButton * createPushButton() { return new QPushButton; }
}
现在它们的风格就是这样的, 我客户端使用起来的伪代码可能是这样的:
AbstractFactory *factory = new QConcreteFactory();
Label *label = factory->createLable();
LineEdit *edit = factory->createLineEdit();
PushButton *btn = facotory->createPushButton();
// 后面是使用 label , edit , btn 做一些布局操作等等等
现在我需要将客户端整体风格换成 A
风格的,那么在保证不影响原来代码的前提下, 我只需要实现 A
风格的工厂 AConcreteFactory
, 以及其下 ALabel
ALineEdit
APushButton
这样客户端在决定使用 A
风格的控件的时候,客户端方面的代码只需要改一行
// AbstractFactory *factory = new QConcreteFactory();
AbstractFactory *factory = new AConcreteFactory();
从这个例子,可以看出一些 抽象工厂的一些很明显的特点:
- 工厂提供了产品的创建接口, 客户端通过工厂提供的创建接口得到产品后可以任意组合,而产品只要关心自己的功能和逻辑即可
- 工厂的存在意义是,它将一个系列相关的产品封装在一个类里,其实封装后,也有一个强调的意思,只要你用这个工厂,那从这个工厂里得到的产品都是它旗下的系列
优缺点
因为很类似于工厂方法,工厂方法有的优点它也有:
- 由于一个工厂封装了产品对象的责任和过程,它将客户与类的实现分离,客户通过它们抽象的接口操作实例,产品的类名也在具体工厂的实现中被分离
此外这种设计也带来一些其他优点:
容易交换产品系列
客户端方面的代码仅仅需要更换使用的具体工厂类即可,改动最小,也很可靠有利于产品的一致性
具体工厂封装了旗下的产品,只要使用一个工厂,就能保证产出的产品是一致的
缺点当然也有:
从我举的例子里,可以看出,当我需要添加一种的新的产品风格的时候,只需要实现一种新的 ConcreteFactory
即可
但是当需要添加一个新种类的产品,比如我从原来 Label
LineEdit
PushButton
再额外加一种 TimeEdit
由于 AbstractFactory
接口确定了可以被创建的产品集合,当添加一种新的产品时,所有涉及到 AbstractFactory
类及其所有子类都需要改变,也就破坏了封闭-开发原则
工厂模式三者的异同
相同点
所谓工厂,客户只要产品,不关心工厂是如何创建的,客户依赖于产品抽象的接口操作实例,也不关心具体产品的具体类名,具体的产品和客户完全隔离开, 这一点,他们都很好的满足
不同点
- 简单工厂: 有 唯一 的工厂类,工厂类的创建方式根据传入的参数判断,最后决定创建的产品
- 工厂方法: 将工厂进行抽象,具体工厂类实现抽象工厂提供的实例化抽象产品的接口,此时 一个工厂是创建一种产品
- 抽象工厂: 抽象工厂将产品进行分类,此时的 一个工厂可以创建一个系列的产品 ,也就是说抽象工厂提供了多个创建产品的接口
换个角度
抽象工厂产品的归类,其实也有一些很有意思的角度:
还是用前面提到的例子来说明谈一谈:
我有3个产品基类分别是 Label
LineEdit
PushButton
, 按照要求我要设计不同的款式 A
和 Q
等等款式
如果从单一产品的角度而言,我完全可以以简单工厂或者说工厂方法来实现, 以 Label
为例:
简单工厂的写法可能类似于下面这样的
enum LabelType
{
QLabel,
ALabel
};
class SimpleFactory
{
public:
static Label * CreateLabel(LabelType type)
{
Label * p = nullptr;
switch(type)
{
case LabelType::QLabel:
p = new QLabel();
break;
case LabelType::ALabel:
p = new ALabel();
break;
default:
p = new QLabel();
break;
}
return p;
}
};
工厂方法,因为我需要提供一个 Label
给基类的工厂定义一个返回 Label
的接口,具体工程继承实现即可:
class Factory
{
public:
virtual Label *CreateLabel()=0;
};
class QLabelFactory : public Factory
{
public:
Label *CreateLabel() {
return new QLabel();
}
};
class ALabelFactory : public Factory
{
public:
Label *CreateLabel() {
return new ALabel();
}
};
但是抽象工厂不是站在单一产品的角度进行抽象,而是对所有产品中一些其他属性进行抽象, 比如 A
, Q
这种风格进行抽象,它将所有产品划分为一个系列,一个产品族
换句话说就是:简单工厂和工厂方法是先划分产品然后再划分风格,而抽象工厂是在先划分风格然后划分产品
抽象的角度不同可能导致使用不同的设计模式,设计出的代码复杂也会存在较大的差异,但可能也是设计的魅力吧
说明
关于抽象工厂模式 C++ 实现的 demo 我就不再这篇文章中直接粘贴了,有兴趣可以关注一下我的 github, 这个工程有我目前整理完的设计模式的 C++ demo,之后会陆续将所有的设计模式全部整理完,加油: