【C++设计模式】抽象工厂模式(Abstract Factory)
系列文章目录
抽象工厂模式(Abstract Factory)
1.简介
抽象工厂模式是一种创建型设计模式,它提供了一种将相关/依赖对象组合在一起创建的方式,而无需指定它们的具体类。
抽象工厂模式与工厂方法模式的区别:
-
工厂方法模式将对象的创建过程延迟到子类中,允许用户通过一个统一的接口来请求他们想要的具体产品,而无需知道产品的具体实现。在这个模式中,工厂类只负责生产同一系列的产品,但具体哪种产品由子类决定。
-
抽象工厂模式更进一步,它提供了一个接口,用于创建一系列相关的或相互依赖的对象,而不是单一对象。抽象工厂封装了产品系列的创建逻辑,客户端不需要知道如何创建具体的产品对象,只需要关心最终能得到哪些产品。这种模式通常用于解决产品集的变化而无需修改客户端代码的情况。
工厂方法模式适用于一个工厂生产一个产品的需求,抽象工厂模式适用于一个工厂生产多个产品(一个产品族)的需求。另外,无论是产品族数量较多还是产品等级结构数量较多,抽象工厂的优势都将更加明显。
2.实现要点
包含组件:
- 抽象工厂(AbstractFactory):它声明了创建一系列具有相同接口的对象的方法,这些对象代表不同的产品。
- 具体工厂(ConcreteFactory):是抽象工厂的具体实现,负责创建具体产品对象。每个具体工厂对应了一系列的具体产品。
- 抽象产品(AbstractProduct):它声明了产品对象的接口,包含了创建产品的公共方法。
- 具体产品(ConcreteProduct):是抽象产品的具体实现。
组件之间的工作步骤如下:
- 定义一个抽象工厂类, 这个类声明了一系列产品的创建方法,但不提供方法的具体代码实现。
- 代码在特定的上下文环境中调用具体工厂对象来负责创建一系列的产品。
- 当客户端需要一个产品时,它向抽象工厂发送请求。抽象工厂根据当前需求选择合适的具体工厂,并由该工厂返回所需的产品实例。
抽象工厂模式将具体产品的创建和使用进行了解耦,使得客户端与具体产品的实现分离,能够很方便地替换不同的产品系列。
3.实现示例
继续使用上一篇工厂方法模式的例子,前面开发的单机闯关打斗类游戏,随着游戏内容越来越丰富,游戏中战斗场景(关卡)数量和类型不断增加,从原来的在城镇中战斗逐步进入在沼泽地战斗、在山脉地区战斗等。
于是,策划把怪物种类进一步按照场景进行了分类,
- 怪物目前仍旧保持2类:元素类和机械类。
- 战斗场景分为2类:沼泽和城镇。
这样来划分的话,整个游戏中目前就有2*2类怪物:沼泽地区的元素类、机械类怪物;城镇中的元素类、机械类怪物。策划规定每个区域的同类型怪物能力上差别很大,例如,沼泽地中的元素类怪物攻击力比城镇中的元素类怪物高很多,城镇地区的机械类怪物会比沼泽地区的机械类怪物生命值高许多。
这样看起来,从怪物父类Monster继承而来的怪物子类就会由原来的2种M_Element、M_Mechanic变为4种,按照这样的怪物分类方式,使用工厂方法模式创建怪物对象则需要创建多达4个工厂子类,但如果一个工厂子类能够生产不止一种具有相同规
则的怪物对象,那么就可以有效地减少所创建的工厂子类数量,这就是抽象工厂(Abstract Factory)模式的核心思想。
有两个概念在抽象工厂模式中经常被提及,分别是“产品等级结构”和“产品族”。绘制一个坐标轴,把前述的4种怪物放人其中:
| 产品等级结构→ ↓产品族 | 元素怪 | 机械怪 |
|---|---|---|
| 城镇工厂 | 城镇 元素怪 | 城镇 机械怪 |
| 沼泽工厂 | 沼泽 元素怪 | 元素 机械怪 |
在上图中,所需的2个工厂分别是Y轴的2个。抽象工厂模式是按照产品族来生产产品,一个地方一个工厂,这个工厂就负责生产本产地的所有产品。
那么我们保留之前的Monster父类,删除原来的2个怪物子类,重新引入4个怪物。
// 沼泽元素类怪物
class M_Element_Swamp : public Monster {
public:
M_Element_Swamp(int life, int magic, int attack) : Monster(life, magic, attack) {
cout << "一只沼泽的元素类怪物来到了这个世界" << endl;
}
};
// 沼泽机械类怪物
class M_Mechanic_Swamp : public Monster {
public:
M_Mechanic_Swamp(int life, int magic, int attack) : Monster(life, magic, attack) {
cout << "一只沼泽的机械类怪物来到了这个世界" << endl;
}
};
// 城镇元素类怪物
class M_Element_Town : public Monster {
public:
M_Element_Town(int life, int magic, int attack) : Monster(life, magic, attack) {
cout << "一只城镇的元素类怪物来到了这个世界" << endl;
}
};
// 城镇机械类怪物
class M_Mechanic_Town : public Monster {
public:
M_Mechanic_Town(int life, int magic, int attack) : Monster(life, magic, attack) {
cout << "一只城镇的机械类怪物来到了这个世界" << endl;
}
};
// 工厂父类
class M_ParFactory {
public:
virtual unique_ptr<Monster> createElement() = 0; // 创建元素类怪物
virtual unique_ptr<Monster> createMechanic() = 0; // 创建机械类怪物
virtual ~M_ParFactory() {} // 虚析构函数
};
// 沼泽工厂
class M_Factory_Swamp : public M_ParFactory {
public:
unique_ptr<Monster> createElement() override {
return make_unique<M_Element_Swamp>(200,80,110); // 创建沼泽元素类怪物
}
unique_ptr<Monster> createMechanic() override {
return make_unique<M_Mechanic_Swamp>(400,0,90); // 创建沼泽机械类怪物
}
};
// 城镇工厂
class M_Factory_Town : public M_ParFactory {
public:
unique_ptr<Monster> createElement() override {
return make_unique<M_Element_Town>(200,80,110); // 创建城镇元素类怪物
}
unique_ptr<Monster> createMechanic() override {
return make_unique<M_Mechanic_Town>(400,0,90); // 创建城镇机械类怪物
}
};
//使用工厂创建怪物
int main() {
// 创建不同的工厂实例
unique_ptr<M_ParFactory> swampFactory = make_unique<M_Factory_Swamp>();
unique_ptr<M_ParFactory> townFactory = make_unique<M_Factory_Town>();
// 使用工厂创建怪物实例
auto swampElement = swampFactory->createElement();
auto townMechanic = townFactory->createMechanic();
return 0; // 返回0表示程序成功结束
}
稳定与变化:
- 稳定部分:
Monster类和M_ParFactory类是稳定部分,不需要频繁修改。- 变化部分:具体怪物类(如
M_Element_Swamp等)和具体工厂类(如M_Factory_Swamp等)属于变化部分。当需要添加新类型的怪物时,只需增加新的具体工厂类和具体怪物类,而不需要修改稳定部分。
扩展性:
如果果游戏中的战斗场景新增加一个森林类型的场景而怪物种类不变(依旧是元素类怪物和机械类怪物),则只需要增加一个新的子工厂类,并继承自
M_ParFactory,而后在新的子工程类中实现createMonster_Element、createMonster_Mechanic虚函数(接口)即可。这种代码实现方式符合开闭原则,也就是通过增加新代码而不是修改原有代码来为游戏增加新功能(对森林类型场景中怪物的创建支持)。如果游戏中新增加了一个新的怪物种类龙类,则此时不但要新增2个继承自Monster的子类来分别支持沼泽龙类怪物、城镇龙类怪物,还必须修改工厂父类M_ParFactory来增加新的虚函数以支持创建龙类怪物,各个工厂子类也需要增加对新怪物的支持。这种在工厂类中通过修改已有代码来扩充游戏功能的方式显然不符合开闭原则。所以此种情况下不适合使用抽象工厂模式。
隐藏实现细节:
如果M_ParFactory类及其具体实现由第三方开发,开发者只需通过抽象工厂接口与工厂交互,而无需了解具体的怪物类(如M_Undead_Swamp等),实现了对具体实现的隐藏。
更多推荐



所有评论(0)