第三章 工厂模式、原型(Prototype)模式、建造者(Builder)模式
第三章 工厂模式、原型(Prototype)模式、建造者(Builder)模式
第一节 工厂模式
1.1简单工厂模式
1.1.1课程背景与章节定位
- 承接与过渡
- 上一章完成《第二章 模板方法模式》讲解,本节进入《第三章 工厂模式、原型模式、建造者模式》(三者均为创建型模式,因关联紧密合并讲解)。
- 本节聚焦第三章第一节:工厂模式的细分 —— 简单工厂模式(工厂模式含简单工厂、工厂方法、抽象工厂三类,本节为基础)。
- 核心目标
- 理解简单工厂模式的设计动机(解决传统对象创建的耦合问题)、实现逻辑、优缺点。
- 对比传统
new创建对象与工厂模式的差异,掌握 “创建代码与业务逻辑隔离” 的设计思想。 - 初步接触面向对象设计原则之 “开闭原则”,学会权衡代码可读性与可扩展性。
1.1.2简单工厂模式的设计动机(为什么需要它?)
(1)传统对象创建的痛点(new关键字的问题)
- 紧耦合依赖:创建类对象时直接使用
new 具体类名()(如Monster* p = new UndeadMonster(300,50,80)),代码需依赖具体类名,若后续修改类(如增加构造函数参数),所有创建该对象的代码都需修改(例:给UndeadMonster加 “防御值” 参数,所有new UndeadMonster的地方都要补参数)。 - 创建与业务混杂:对象创建代码(
new)与业务逻辑代码(如调用对象的attack()方法)混在一起,代码可维护性差,修改创建逻辑需遍历所有业务代码。
(2)工厂模式的核心解决思路
- 封装创建逻辑:将 “对象创建代码” 统一包装到一个 “工厂类” 中,业务逻辑只需调用工厂类的接口,无需直接接触
new和具体类名。 - 隔离依赖范围:将对具体类的依赖从 “所有业务代码” 缩小到 “工厂类”,后续修改创建逻辑(如加参数、加新类),仅需修改工厂类,业务代码保持稳定(即 “封装变化”)。
1.1.3简单工厂模式的实现(以 “游戏怪物创建” 为例)
(1)业务场景定义
- 游戏需求:创建三类怪物(亡灵类
UndeadMonster、元素类ElementMonster、机械类MechanicMonster),所有怪物均有 “生命值、魔法值、攻击力” 属性,且需支持多态。
(2)代码实现步骤(核心逻辑)
步骤 1:定义怪物父类
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时析构函数应该为虚函数
protected://可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
步骤 2:定义怪物子类(具体实现)
//亡灵类怪物
class M_Undead :public Monster
{
public:
//构造函数
M_Undead(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个亡灵类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//元素类怪物
class M_Element :public Monster
{
public:
//构造函数
M_Element(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个元素类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//机械类怪物
class M_Mechanic :public Monster
{
public:
//构造函数
M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个机械类怪物来到了这个世界" << endl;
}
//其他代码略......
};
步骤 3:创建 “怪物工厂类”(封装创建逻辑)
//怪物工厂类
class MonsterFactory
{
public:
//Monster* createMonster(string strmontype) //简单工厂模式
static Monster* createMonster(string strmontype) //静态工厂方法模式(Static Factory Method)
{
Monster* prtnobj = nullptr;
if (strmontype == "udd") //udd代表要创建亡灵类怪物
{
prtnobj = new M_Undead(300, 50, 80);
}
else if (strmontype == "elm") //elm代表要创建元素类怪物
{
prtnobj = new M_Element(200, 80, 100);
}
else if (strmontype == "mec") //mec代表要创建机械类怪物
{
prtnobj = new M_Mechanic(400, 0, 110);
}
return prtnobj;
}
};
步骤 4:业务代码调用(无直接new,依赖工厂)
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口
// 1. 普通调用(创建工厂对象)
_nmsp1::Monster* pM1 = new _nmsp1::M_Undead(300, 50, 80); //产生一只亡灵类怪物
_nmsp1::Monster* pM2 = new _nmsp1::M_Element(200, 80, 100); //产生一只元素类怪物
_nmsp1::Monster* pM3 = new _nmsp1::M_Mechanic(400, 0, 110); //产生一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
/*
// 2. 普通工厂调用(创建工厂对象)
_nmsp1::MonsterFactory facobj;
_nmsp1::Monster* pM1 = facobj.createMonster("udd"); //产生了一只亡灵类怪物,当然这里必须知道“udd”代表的是创建亡灵类怪物
_nmsp1::Monster* pM2 = facobj.createMonster("elm"); //创建一只元素类怪物
_nmsp1::Monster* pM3 = facobj.createMonster("mec"); //创建一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
/*
// 3. 静态工厂调用(无需工厂对象)
//此时简单工厂模式又可以称为静态工厂方法模式(Static Factory Method)
_nmsp1::Monster* pM1 = _nmsp1::MonsterFactory::createMonster("udd"); //产生了一只亡灵类怪物,当然这里必须知道“udd”代表的是创建亡灵类怪物
_nmsp1::Monster* pM2 = _nmsp1::MonsterFactory::createMonster("elm"); //创建一只元素类怪物
_nmsp1::Monster* pM3 = _nmsp1::MonsterFactory::createMonster("mec"); //创建一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
return 0;
}
完整代码:
// MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//公众号:程序员速成 ,内含一辈子都让你感激自己的优质视频教程,欢迎关注
#include <iostream>
#include <vector>
#include <sstream>
#ifdef _DEBUG //只在Debug(调试)模式下
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符
#define new DEBUG_NEW
#endif
#endif
//#include <boost/type_index.hpp>
using namespace std;
//#pragma warning(disable : 4996)
namespace _nmsp1
{
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时析构函数应该为虚函数
protected://可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
//亡灵类怪物
class M_Undead :public Monster
{
public:
//构造函数
M_Undead(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个亡灵类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//元素类怪物
class M_Element :public Monster
{
public:
//构造函数
M_Element(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个元素类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//机械类怪物
class M_Mechanic :public Monster
{
public:
//构造函数
M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个机械类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//-------------------------------
//怪物工厂类
class MonsterFactory
{
public:
//Monster* createMonster(string strmontype) //简单工厂模式
static Monster* createMonster(string strmontype) //静态工厂方法模式(Static Factory Method)
{
Monster* prtnobj = nullptr;
if (strmontype == "udd") //udd代表要创建亡灵类怪物
{
prtnobj = new M_Undead(300, 50, 80);
}
else if (strmontype == "elm") //elm代表要创建元素类怪物
{
prtnobj = new M_Element(200, 80, 100);
}
else if (strmontype == "mec") //mec代表要创建机械类怪物
{
prtnobj = new M_Mechanic(400, 0, 110);
}
return prtnobj;
}
};
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口
//第三章 工厂模式、原型(Prototype)模式、建造者(Builder)模式
//第一节 工厂模式:创建型模式
//new,堆对象,可以实现多态。
//工厂模式:通过把创建对象的代码包装起来,做到创建对象的代码与具体的业务逻辑代码相隔离的目的。
//A *pobja = new A();
//工厂模式细分:a)简单工厂模式;b)工厂方法模式;c)抽象工厂模式。
//面向对象程序设计一个重要原则:开闭原则。
//(1)简单工厂(Simple Factory)模式
//策划:亡灵类怪物,元素类怪物,机械类怪物:都有生命值,魔法值,攻击力三个属性。
//Monster作为父类,M_Undead(亡灵类),M_Element(元素类怪物),M_Mechanic(机械类怪物)。
//使用new +具体类名来创建对象是一种 依赖具体类型的紧耦合关系。
//简单工厂模式的实现思路:使用工厂类可以实现创建怪物的代码 与 各个 具体怪物类对象要实现的逻辑代码隔离。
//封装变化:把依赖范围尽可能缩小,把容易变化的代码段限制在一个小范围内,就可以在很大程度上提高代码的可维护性和可扩展性。
//通过增加新的if else分支来达到支持新怪物增加的目的——违背了面向对象程序设计的原则:开闭原则(Open Close Principle :OCP);
//开闭原则:说的是代码扩展性问题——对扩展开放,对修改关闭(封闭);
//开闭原则详细的解释:当增加新功能,不应该通过修改已经存在的代码来进行,而是应该通过扩展代码(比如增加新类,增加新成员函数)来进行。
//如果if,else分支不多(没有数十上百个),则适当违反开闭原则,是完全可以接受的。
//大家要在代码的可读性和可扩展ing之间做出权衡。
//引入“简单工厂”设计模式的定义(实现意图):定义一个工厂类(MonsterFactory),该类的成员函数(createMonster)可以根据不同参数
//创建并返回不同的类对象,被创建的对象所属的类(M_Undead,M_Element,M_Mechanic)一般都具有相同的父类(Monster),
//调用者(这里指main函数)无需关心创建对象的细节。
//简单工厂方法模式:实现了创建怪物类代码(createMonster),与具体怪物类(M_Undead,M_Element,M_Mechanic)解耦合的效果。
/*
_nmsp1::Monster* pM1 = new _nmsp1::M_Undead(300, 50, 80); //产生一只亡灵类怪物
_nmsp1::Monster* pM2 = new _nmsp1::M_Element(200, 80, 100); //产生一只元素类怪物
_nmsp1::Monster* pM3 = new _nmsp1::M_Mechanic(400, 0, 110); //产生一只机械类怪物
//pM2->attack
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
/*_nmsp1::MonsterFactory facobj;
_nmsp1::Monster* pM1 = facobj.createMonster("udd"); //产生了一只亡灵类怪物,当然这里必须知道“udd”代表的是创建亡灵类怪物
_nmsp1::Monster* pM2 = facobj.createMonster("elm"); //创建一只元素类怪物
_nmsp1::Monster* pM3 = facobj.createMonster("mec"); //创建一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
//此时简单工厂模式又可以称为静态工厂方法模式(Static Factory Method)
_nmsp1::Monster* pM1 = _nmsp1::MonsterFactory::createMonster("udd"); //产生了一只亡灵类怪物,当然这里必须知道“udd”代表的是创建亡灵类怪物
_nmsp1::Monster* pM2 = _nmsp1::MonsterFactory::createMonster("elm"); //创建一只元素类怪物
_nmsp1::Monster* pM3 = _nmsp1::MonsterFactory::createMonster("mec"); //创建一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
return 0;
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门使用技巧:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
1.1.4简单工厂模式的核心特点与定义
(1)模式定义(官方 + 通俗解读)
- 官方定义:定义一个工厂类,其成员函数可根据不同参数创建并返回不同类的对象,且被创建对象的类需具有相同父类;调用者无需关心对象创建细节。
- 通俗解读:“专门找个‘工厂’帮你造东西,你只说要啥(参数),不用管怎么造(
new和具体类名),拿到东西直接用(业务逻辑)”。
(2)关键特征
| 特征 | 说明 |
|---|---|
| 单一工厂类 | 仅需一个工厂类(如MonsterFactory),统一管理所有对象的创建逻辑。 |
| 参数驱动创建 | 通过参数(如字符串 “undead”)指定创建的对象类型,而非硬编码具体类名。 |
| 返回父类指针 | 工厂方法返回父类指针(如Monster*),天然支持多态,业务代码可统一调用父类接口。 |
| 隐藏创建细节 | 调用者无需知道new的具体过程、子类名,只需知道 “参数对应哪种对象”。 |
1.1.5优缺点分析与 “开闭原则”
(1)优点
- 降低耦合:业务代码与具体类解耦(无需写
new 子类名),修改子类构造函数时,仅需改工厂类,无需改所有业务代码。 - 提高可维护性:创建逻辑集中在工厂类,便于统一管理(如加日志、加参数校验)。
- 隐藏实现细节:可隐藏具体类名(如第三方库提供工厂接口,不暴露内部类名),降低调用者学习成本。
(2)缺点(核心:违反开闭原则)
- 扩展新类型需修改工厂:若新增怪物类型(如 “鱼类怪物
FishMonster”),需在工厂方法中加else if分支(修改已有代码),违反 “开闭原则”。 - 工厂类可能臃肿:若对象类型极多(数十上百种),工厂方法的
if-else分支会非常多,可读性和维护性下降。
(3)与 “开闭原则” 的关系
- 开闭原则定义:面向对象设计的核心原则,指 “对扩展开放(新增功能可加代码),对修改关闭(不改已有代码)”。
- 简单工厂的权衡:
- 若对象类型少(如仅 3-5 种,分支少),适当违反开闭原则是可接受的—— 代码简单易懂,维护成本低。
- 若对象类型多且频繁新增,需用后续 “工厂方法模式” 解决(遵循开闭原则,但代码更复杂)。
- 核心启示:设计模式需 “因地制宜”,不盲目追求原则,权衡 “可读性” 与 “可扩展性”。
1.1.6补充说明(易混淆点与课程预告)
- 简单工厂的定位:
- 不算 “标准设计模式”(四人组《设计模式:可复用面向对象软件的基础》中未收录),更像 “编程手法” 或 “工厂模式的入门版”,适合作为工厂方法、抽象工厂模式的学习基础。
- 静态工厂方法的别称:
- 若工厂方法设为
static(无需创建工厂对象),该模式也称为 “静态工厂方法模式”,本质仍是简单工厂。
- 若工厂方法设为
- 后续课程预告:
- 本章将继续讲解 “工厂方法模式”(解决简单工厂违反开闭原则的问题)和 “抽象工厂模式”(应对多产品族创建),最终对比工厂、原型、建造者三种创建型模式的适用场景。
1.2工厂方法模式
1.2.1课程背景与概念辨析
- 章节定位
- 本节紧接简单工厂模式,讲解工厂模式体系中使用频率最高、最标准的模式 —— 工厂方法模式。
- 别名:常被直接称为“工厂模式”或“多态工厂模式”。
- 与简单工厂的关系
- 思维演变:很多资料将简单工厂视为工厂方法的一个特例。但本课程将其分开,体现从“违背开闭原则”到“符合开闭原则”的过程。
- 本质联系:一般可以认为,将简单工厂模式的代码经过把“工厂类”进行抽象,改造成符合开闭原则后的代码,就变成了工厂方法模式的代码。
- 对比:简单工厂模式把创建对象这件事放到了一个统一的地方来处理,弹性比较差;而工厂方法模式相当于建立了一个程序实现框架,从而让子类来决定对象如何创建。
1.2.2 设计动机(为什么要升级?)
(1)简单工厂模式的局限性(违背 OCP)
- 痛点回顾:在简单工厂中,如果想新增一种怪物(如“野兽类”),必须修改
MonsterFactory类的createMonster函数,增加新的else if分支。 - 理论冲突:这种做法违背了面向对象设计的核心原则 —— 开闭原则(Open Close Principle),即“对扩展开放,对修改关闭”。
(2)工厂方法模式的解决思路
- 核心策略:不再用一个工厂类处理所有对象的创建,而是为每种怪物单独创建一个对应的工厂类。
- 架构调整:
- 建立一个工厂抽象基类(父类)。
- 具体的创建逻辑下沉到工厂子类中实现。
- 当新增怪物时,只需增加一个新的“怪物类”和一个新的“怪物工厂类”,无需修改任何现有代码。
1.2.3 工厂方法模式的实现
(1)类结构设计(平行层级结构)
工厂方法模式通常表现为两个平行的继承层级,这也增加了新类的层次结构和数目:
- 产品等级结构:
Monster(父) ->M_Undead,M_Element,M_Mechanic(子)。 - 工厂等级结构:
M_ParFactory(父) ->M_UndeadFactory,M_ElementFactory,M_MechanicFactory(子)。 - 规律:工厂方法模式往往需要创建一个与产品等级结构(层次)相同的工厂等级结构。
(2)代码实现步骤
步骤 1:定义工厂抽象父类(接口)
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时析构函数应该为虚函数
protected://可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
步骤 2:定义具体的工厂子类
为每种怪物定义对应的工厂,负责具体的 new 操作和初始化业务。
//亡灵类怪物
class M_Undead :public Monster
{
public:
//构造函数
M_Undead(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个亡灵类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//元素类怪物
class M_Element :public Monster
{
public:
//构造函数
M_Element(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个元素类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//机械类怪物
class M_Mechanic :public Monster
{
public:
//构造函数
M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个机械类怪物来到了这个世界" << endl;
}
//其他代码略......
};
步骤 3:定义工厂抽象父类(工厂接口)
//所有工厂类的父类
class M_ParFactory
{
public:
virtual Monster* createMonster() = 0; //具体的实现在子类中进行
virtual ~M_ParFactory() {} //做父类时析构函数应该为虚函数
};
步骤 4:定义具体的工厂子类(实现创建逻辑)
-
M_UndeadFactory,M_ElementFactory,M_MechanicFactory类,有一个共同的父类M_ParFactory(工厂抽象类)。 -
符合开闭原则,付出的代价是需要新增加多个新的工厂类。
-
定义(实现意图):定义一个用于创建对象的接口(
M_ParFactory类中的createMonster成员函数,这其实就是个工厂方法,工厂方法模式的名字也是由此而来),但由子类(M_UndeadFactory、M_ElementFactory、M_MechanicFactory)决定要实例化的类是哪一个。 -
该模式使得某个类(
M_Undead、M_Element、M_Mechanic)的实例化延迟到子类(M_UndeadFactory、M_ElementFactory、M_MechanicFactory)。 -
出现新怪物类型:
M_Beast(野兽类)。一般可以认为,将简单工厂模式的代码经过把工厂类进行抽象改造成符合开闭原则后的代码,就变成了工厂方法模式的代码。
-
把用new创建对象集中到某个或者某些工厂类的成员函数中去做,的好处:
-
封装变化。
prtnobj = new M_Element(200,80,100); ->new M_Element(200,80,100,300); -
创建对象前需要一些额外的业务代码,可以将这些代码增加到具体的工厂类的
createMonster成员函数中。
-
//M_Undead怪物类型的工厂,生产M_Undead类型怪物
class M_UndeadFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
//return new M_Undead(300, 50, 80); //创建亡灵类怪物
Monster *ptmp = new M_Undead(300, 50, 80); //创建亡灵类怪物
//这里可以增加一些其他业务代码
//......
return ptmp;
}
};
//M_Element怪物类型的工厂,生产M_Element类型怪物
class M_ElementFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Element(200, 80, 100); //创建元素类怪物
}
};
//M_Mechanic怪物类型的工厂,生产M_Mechanic类型怪物
class M_MechanicFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Mechanic(400, 0, 110); //创建机械类怪物
}
};
步骤 5:全局函数与业务调用(多态应用)
//全局的用于创建怪物对象的函数,注意形参的类型是工厂父类类型的指针,返回类型是怪物父类类型的指针
Monster* Gbl_CreateMonster(M_ParFactory* factory)
{
return factory->createMonster(); //createMonster虚函数扮演了多态new的行为,factory指向的具体怪物工厂类不同,创建的怪物对象也不同。
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口
/* // 工厂方法模式调用示例
_nmsp1::M_ParFactory* p_ud_fy = new _nmsp1::M_UndeadFactory(); //多态工厂,注意指针类型
_nmsp1::Monster* pM1 = _nmsp1::Gbl_CreateMonster(p_ud_fy); //产生了一只亡灵类怪物,也是多态,注意返回类型
//当然,这里也可以直接写成 Monster *pM1 = p_ud_fy->createMonster();
_nmsp1::M_ParFactory* p_elm_fy = new _nmsp1::M_ElementFactory();
_nmsp1::Monster *pM2 = _nmsp1::Gbl_CreateMonster(p_elm_fy); //产生了一只元素类怪物
_nmsp1::M_ParFactory* p_mec_fy = new _nmsp1::M_MechanicFactory();
_nmsp1::Monster* pM3 = _nmsp1::Gbl_CreateMonster(p_mec_fy); //产生了一只机械类怪物
//释放资源
//释放工厂
delete p_ud_fy;
delete p_elm_fy;
delete p_mec_fy;
//释放怪物
delete pM1;
delete pM2;
delete pM3;
*/
return 0;
}

完整代码:
// MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//公众号:程序员速成 ,内含一辈子都让你感激自己的优质视频教程,欢迎关注
#include <iostream>
#include <vector>
#include <sstream>
#ifdef _DEBUG //只在Debug(调试)模式下
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符
#define new DEBUG_NEW
#endif
#endif
//#include <boost/type_index.hpp>
using namespace std;
//#pragma warning(disable : 4996)
namespace _nmsp1
{
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时析构函数应该为虚函数
protected://可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
//亡灵类怪物
class M_Undead :public Monster
{
public:
//构造函数
M_Undead(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个亡灵类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//元素类怪物
class M_Element :public Monster
{
public:
//构造函数
M_Element(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个元素类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//机械类怪物
class M_Mechanic :public Monster
{
public:
//构造函数
M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个机械类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//野兽类
//class M_Beast :public Monster { .... };
//-------------------------------
/*
//怪物工厂类
class MonsterFactory
{
public:
//Monster* createMonster(string strmontype) //简单工厂模式
static Monster* createMonster(string strmontype) //静态工厂方法模式(Static Factory Method)
{
Monster* prtnobj = nullptr;
if (strmontype == "udd") //udd代表要创建亡灵类怪物
{
prtnobj = new M_Undead(300, 50, 80);
}
else if (strmontype == "elm") //elm代表要创建元素类怪物
{
prtnobj = new M_Element(200, 80, 100);
}
else if (strmontype == "mec") //mec代表要创建机械类怪物
{
prtnobj = new M_Mechanic(400, 0, 110);
}
return prtnobj;
}
};
*/
//所有工厂类的父类
class M_ParFactory
{
public:
virtual Monster* createMonster() = 0; //具体的实现在子类中进行
virtual ~M_ParFactory() {} //做父类时析构函数应该为虚函数
};
//M_Undead怪物类型的工厂,生产M_Undead类型怪物
class M_UndeadFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
//return new M_Undead(300, 50, 80); //创建亡灵类怪物
Monster *ptmp = new M_Undead(300, 50, 80); //创建亡灵类怪物
//这里可以增加一些其他业务代码
//......
return ptmp;
}
};
//M_Element怪物类型的工厂,生产M_Element类型怪物
class M_ElementFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Element(200, 80, 100); //创建元素类怪物
}
};
//M_Mechanic怪物类型的工厂,生产M_Mechanic类型怪物
class M_MechanicFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Mechanic(400, 0, 110); //创建机械类怪物
}
};
//class M_BeastFactory:public M_ParFactory{......};
//全局的用于创建怪物对象的函数,注意形参的类型是工厂父类类型的指针,返回类型是怪物父类类型的指针
Monster* Gbl_CreateMonster(M_ParFactory* factory)
{
return factory->createMonster(); //createMonster虚函数扮演了多态new的行为,factory指向的具体怪物工厂类不同,创建的怪物对象也不同。
}
//-------------------
//不想创建太多工厂类,又想封装变化
//创建怪物工厂子类模板
template <typename T>
class M_ChildFactory :public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new T(300, 50, 80); //如果需要不同的值则可以通过createMonster的形参将值传递进来
}
};
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口
//第三章 工厂模式、原型(Prototype)模式、建造者(Builder)模式
//第一节 工厂模式:创建型模式
//new,堆对象,可以实现多态。
//工厂模式:通过把创建对象的代码包装起来,做到创建对象的代码与具体的业务逻辑代码相隔离的目的。
//A *pobja = new A();
//工厂模式细分:a)简单工厂模式;b)工厂方法模式;c)抽象工厂模式。
//面向对象程序设计一个重要原则:开闭原则。
//(1)简单工厂(Simple Factory)模式
//策划:亡灵类怪物,元素类怪物,机械类怪物:都有生命值,魔法值,攻击力三个属性。
//Monster作为父类,M_Undead(亡灵类),M_Element(元素类怪物),M_Mechanic(机械类怪物)。
//使用new +具体类名来创建对象是一种 依赖具体类型的紧耦合关系。
//简单工厂模式的实现思路:使用工厂类可以实现创建怪物的代码 与 各个 具体怪物类对象要实现的逻辑代码隔离。
//封装变化:把依赖范围尽可能缩小,把容易变化的代码段限制在一个小范围内,就可以在很大程度上提高代码的可维护性和可扩展性。
//通过增加新的if else分支来达到支持新怪物增加的目的——违背了面向对象程序设计的原则:开闭原则(Open Close Principle :OCP);
//开闭原则:说的是代码扩展性问题——对扩展开放,对修改关闭(封闭);
//开闭原则详细的解释:当增加新功能,不应该通过修改已经存在的代码来进行,而是应该通过扩展代码(比如增加新类,增加新成员函数)来进行。
//如果if,else分支不多(没有数十上百个),则适当违反开闭原则,是完全可以接受的。
//大家要在代码的可读性和可扩展ing之间做出权衡。
//引入“简单工厂”设计模式的定义(实现意图):定义一个工厂类(MonsterFactory),该类的成员函数(createMonster)可以根据不同参数
//创建并返回不同的类对象,被创建的对象所属的类(M_Undead,M_Element,M_Mechanic)一般都具有相同的父类(Monster),
//调用者(这里指main函数)无需关心创建对象的细节。
//简单工厂方法模式:实现了创建怪物类代码(createMonster),与具体怪物类(M_Undead,M_Element,M_Mechanic)解耦合的效果。
//(2)工厂方法(Factory Method)模式:简称工厂模式或者多态工厂模式。
//与简单工厂模式比,灵活性更强,实现也更加复杂,引入更多的新类。
//M_UndeadFactory,M_ElementFactory,M_MechanicFactory类,有一个共同的父类M_ParFactory(工厂抽象类):
//符合开闭原则,付出的代价是需要新增加多个新的工厂类。
//定义(实现意图):定义一个用于创建对象的接口(M_ParFactory类中的createMonster成员函数,这其实就是个工厂方法,工厂方法模式的名字也是由此而来),
//但由子类(M_UndeadFactory、M_ElementFactory、M_MechanicFactory)决定要实例化的类是哪一个。
//该模式使得某个类(M_Undead、M_Element、M_Mechanic)的实例化延迟到子类(M_UndeadFactory、M_ElementFactory、M_MechanicFactory)。
//出现新怪物类型:M_Beast(野兽类)。
//一般可以认为,将简单工厂模式的代码经过把工厂类进行抽象改造成符合开闭原则后的代码,就变成了工厂方法模式的代码。
//把用new创建对象集中到某个或者某些工厂类的成员函数中去做,的好处:
//a)封装变化。 prtnobj = new M_Element(200,80,100); ->new M_Element(200,80,100,300);
//b)创建对象前需要一些额外的业务代码,可以将这些代码增加到具体的工厂类的createMonster成员函数中。
//简单工厂模式把创建对象这件事放到了一个统一的地方来处理,弹性比较差。而工厂方法模式相当于建立了一个程序实现框架,从而让子类来决定对象如何创建。
//工厂方法模式往往需要创建一个与产品等级结构(层次)相同的工厂等级结构,这也增加了新类的层次结构和数目。
/*
_nmsp1::Monster* pM1 = new _nmsp1::M_Undead(300, 50, 80); //产生一只亡灵类怪物
_nmsp1::Monster* pM2 = new _nmsp1::M_Element(200, 80, 100); //产生一只元素类怪物
_nmsp1::Monster* pM3 = new _nmsp1::M_Mechanic(400, 0, 110); //产生一只机械类怪物
//pM2->attack
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
/*_nmsp1::MonsterFactory facobj;
_nmsp1::Monster* pM1 = facobj.createMonster("udd"); //产生了一只亡灵类怪物,当然这里必须知道“udd”代表的是创建亡灵类怪物
_nmsp1::Monster* pM2 = facobj.createMonster("elm"); //创建一只元素类怪物
_nmsp1::Monster* pM3 = facobj.createMonster("mec"); //创建一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
/*
//此时简单工厂模式又可以称为静态工厂方法模式(Static Factory Method)
_nmsp1::Monster* pM1 = _nmsp1::MonsterFactory::createMonster("udd"); //产生了一只亡灵类怪物,当然这里必须知道“udd”代表的是创建亡灵类怪物
_nmsp1::Monster* pM2 = _nmsp1::MonsterFactory::createMonster("elm"); //创建一只元素类怪物
_nmsp1::Monster* pM3 = _nmsp1::MonsterFactory::createMonster("mec"); //创建一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
/*
_nmsp1::M_ParFactory* p_ud_fy = new _nmsp1::M_UndeadFactory(); //多态工厂,注意指针类型
_nmsp1::Monster* pM1 = _nmsp1::Gbl_CreateMonster(p_ud_fy); //产生了一只亡灵类怪物,也是多态,注意返回类型
//当然,这里也可以直接写成 Monster *pM1 = p_ud_fy->createMonster();
_nmsp1::M_ParFactory* p_elm_fy = new _nmsp1::M_ElementFactory();
_nmsp1::Monster *pM2 = _nmsp1::Gbl_CreateMonster(p_elm_fy); //产生了一只元素类怪物
_nmsp1::M_ParFactory* p_mec_fy = new _nmsp1::M_MechanicFactory();
_nmsp1::Monster* pM3 = _nmsp1::Gbl_CreateMonster(p_mec_fy); //产生了一只机械类怪物
//释放资源
//释放工厂
delete p_ud_fy;
delete p_elm_fy;
delete p_mec_fy;
//释放怪物
delete pM1;
delete pM2;
delete pM3;
*/
_nmsp1::M_ChildFactory<_nmsp1::M_Undead> myFactory;
_nmsp1::Monster* pM10 = myFactory.createMonster();
//释放资源
delete pM10;
return 0;
}
1.2.4 核心定义与特点
(1)官方定义解析
- 定义:“定义一个用于创建对象的接口(即
M_ParFactory中的createMonster),让子类(具体工厂)决定实例化哪一个类。” - 延迟实例化:“使一个类的实例化延迟到其子类。”(父类只规定‘我要造个怪物’,具体造什么怪物由子工厂决定)。
(2)主要特征
- 核心结构:建立了一个程序实现框架,通过多态让子类决定对象如何创建。
- 扩展性:新增功能(如新增“野兽类
M_Beast”)通过增加新代码(新建M_Beast和M_BeastFactory)实现,而非修改现有代码。
1.2.5 优缺点分析
| 维度 | 说明 |
|---|---|
| 优点 | 1. 符合开闭原则:新增产品无需修改工厂逻辑,扩展性极佳。 2. 封装变化:将创建细节封装在具体工厂内部(如构造参数调整只需改对应工厂)。 3. 屏蔽细节:对于第三方库开发者,可以隐藏具体产品类名,用户只需操作工厂接口。 |
| 缺点 | 1. 类爆炸:每增加一种产品,就需要增加一个对应的工厂类,导致类数量成倍增加。 2. 系统复杂:增加了系统的抽象度和理解难度(需要理解平行的类层级结构)。 |
1.2.6 进阶技巧:模板工厂(减少类数量)
如果不想为每种怪物都写一个工厂类,但又想保持封装性,可以结合 C++ 模板技术实现一个通用的子类工厂。
//不想创建太多工厂类,又想封装变化
//创建怪物工厂子类模板
template <typename T>
class M_ChildFactory :public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new T(300, 50, 80); //如果需要不同的值则可以通过createMonster的形参将值传递进来
}
};
// 使用方式(Main函数中):
// _nmsp1::M_ChildFactory<_nmsp1::M_Undead> myFactory;
// _nmsp1::Monster* pM10 = myFactory.createMonster();
1.2.7学习总结:
工厂方法模式是框架设计的基础。相比简单工厂的“大包大揽”(一个函数搞定所有),工厂方法模式建立了一套“标准制度”(父类定标准,子类去执行)4,虽然代码量变多了,但在大型系统和长期维护中,它提供了极其宝贵的扩展弹性。
1.3抽象工厂模式
1.3.1本节学习目标
- 以芭比娃娃多厂商部件生产为例,掌握抽象工厂模式的实际应用流程
- 理解抽象工厂模式中产品族与产品等级结构的对应关系
- 明确抽象工厂模式与工厂方法模式的核心区别
- 完成三种工厂模式(简单工厂、工厂方法、抽象工厂)的整体总结
1.3.2需求背景
- 芭比娃娃组成:由身体(含头、颈部、躯干、四肢)、衣服、鞋子三个核心部件构成
- 生产厂商:支持中国、日本、美国三个厂商,每个厂商均可生产上述三类部件
- 具体生产要求
- 芭比娃娃 1:身体、衣服、鞋子全部采用中国厂商部件
- 芭比娃娃 2:身体用中国厂商、衣服用日本厂商、鞋子用美国厂商部件
1.3.3类的设计思路(核心)
按照抽象工厂模式的设计逻辑,整体分为 3 个核心步骤(对应代码_nmsp3命名空间):
- 部件抽象化:将身体、衣服、鞋子封装为抽象类,定义统一接口(如
getName()) - 工厂抽象化:定义抽象工厂类,声明生产三类部件的纯虚接口
- 具体实现层:为每个厂商分别实现部件具体类和工厂具体类,绑定厂商与部件的生产关系
1.3.4代码实现拆解
(1)部件抽象类定义
作用:为同类型部件提供统一接口,实现多态调用,隔离具体厂商的部件差异
namespace _nmsp3
{
// 身体抽象类
class Body
{
public:
virtual void getName() = 0; // 纯虚接口,获取部件厂商信息
virtual ~Body() {} // 父类虚析构,保证子类析构正常调用
};
// 衣服抽象类(结构与Body一致)
class Clothes
{
public:
virtual void getName() = 0;
virtual ~Clothes() {}
};
// 鞋子抽象类(结构与Body一致)
class Shoes
{
public:
virtual void getName() = 0;
virtual ~Shoes() {}
};
}
- 每个抽象类仅声明
getName()纯虚函数,具体实现由厂商子类完成;虚析构函数是父类的必要设计,避免内存泄漏
(2)抽象工厂类定义
作用:定义生产产品族(身体 + 衣服 + 鞋子)的统一接口,约束具体工厂的生产能力
namespace _nmsp3
{
class AbstractFactory
{
public:
// 约束:仅生产身体、衣服、鞋子三类稳定部件(新增部件会违反开闭原则)
virtual Body* createBody() = 0; // 生产身体的接口
virtual Clothes* createClothes() = 0; // 生产衣服的接口
virtual Shoes* createShoes() = 0; // 生产鞋子的接口
virtual ~AbstractFactory() {}
};
}
- 抽象工厂的核心是绑定产品族:一个工厂对应一个厂商,可生产该厂商的全套部件;若新增部件(如袜子),需修改此抽象类接口,违背开闭原则,因此抽象工厂适合产品等级结构稳定的场景
(3)芭比娃娃组装类
作用:接收三类部件对象,完成芭比娃娃的组装,并输出部件来源信息
namespace _nmsp3
{
class BarbieDoll
{
public:
// 构造函数:接收三类部件指针,初始化成员变量
BarbieDoll(Body* tmpbody, Clothes* tmpclothes, Shoes* tmpshoes)
{
body = tmpbody;
clothes = tmpclothes;
shoes = tmpshoes;
}
// 组装方法:输出部件厂商信息,模拟组装逻辑
void Assemble()
{
cout << "成功组装了一个芭比娃娃:" << endl;
body->getName(); // 多态调用,输出身体厂商
clothes->getName(); // 多态调用,输出衣服厂商
shoes->getName(); // 多态调用,输出鞋子厂商
}
private:
Body* body; // 身体部件指针
Clothes* clothes; // 衣服部件指针
Shoes* shoes; // 鞋子部件指针
};
}
- 组装类不关心部件的具体厂商,仅依赖抽象类接口,实现了业务逻辑与部件生产的解耦
(4)具体厂商部件类与工厂类
①中国厂商实现(其他厂商逻辑一致,仅厂商标识不同)
namespace _nmsp3
{
// 中国厂商-身体部件(继承自Body抽象类)
class China_Body :public Body
{
public:
virtual void getName()
{
cout << "中国厂商产的_身体部件" << endl;
}
};
// 中国厂商-衣服部件
class China_Clothes :public Clothes
{
public:
virtual void getName()
{
cout << "中国厂商产的_衣服部件" << endl;
}
};
// 中国厂商-鞋子部件
class China_Shoes :public Shoes
{
public:
virtual void getName()
{
cout << "中国厂商产的_鞋子部件" << endl;
}
};
// 中国厂商工厂(继承自抽象工厂,实现全套部件生产)
class ChinaFactory : public AbstractFactory
{
public:
virtual Body* createBody() { return new China_Body; }
virtual Clothes* createClothes() { return new China_Clothes; }
virtual Shoes* createShoes() { return new China_Shoes; }
};
}
- 具体部件类需重写
getName()接口,标识自身厂商;具体工厂类需实现抽象工厂的所有生产接口,绑定本厂商的部件生产
②日本 / 美国厂商实现
逻辑与中国厂商完全一致,仅类名和输出信息中的 “厂商名称” 替换为 “日本” 或 “美国”(如Japan_Body、America_Shoes等),代码可通过 “复制 - 修改标识” 快速实现,体现了抽象工厂模式的扩展性


1.3.5main 函数调用与测试(实现需求)
(1)创建第一个芭比娃娃(全中国部件)
// 1. 创建中国工厂(父类指针指向子类对象,多态工厂)
_nmsp3::AbstractFactory* pChinaFactory = new _nmsp3::ChinaFactory();
// 2. 用中国工厂生产全套中国部件
_nmsp3::Body* pChinaBody = pChinaFactory->createBody();
_nmsp3::Clothes* pChinaClothes = pChinaFactory->createClothes();
_nmsp3::Shoes* pChinaShoes = pChinaFactory->createShoes();
// 3. 组装芭比娃娃并输出信息
_nmsp3::BarbieDoll* pbd1obj = new _nmsp3::BarbieDoll(pChinaBody, pChinaClothes, pChinaShoes);
pbd1obj->Assemble();
- 执行结果:输出 “中国厂商产的_身体部件 / 衣服部件 / 鞋子部件”,满足 “全中国部件” 的需求
(2)创建第二个芭比娃娃(跨厂商部件)
cout << "-------------------------------------" << endl;
// 1. 补充创建日本、美国工厂(复用已有的中国工厂)
_nmsp3::AbstractFactory* pJapanFactory = new _nmsp3::JapanFactory();
_nmsp3::AbstractFactory* pAmericaFactory = new _nmsp3::AmericaFactory();
// 2. 按需求生产跨厂商部件
_nmsp3::Body* pChinaBody2 = pChinaFactory->createBody(); // 中国身体
_nmsp3::Clothes* pJapanClothes = pJapanFactory->createClothes(); // 日本衣服
_nmsp3::Shoes* pAmericaShoes = pAmericaFactory->createShoes(); // 美国鞋子
// 3. 组装并输出信息
_nmsp3::BarbieDoll* pbd2obj = new _nmsp3::BarbieDoll(pChinaBody2, pJapanClothes, pAmericaShoes);
pbd2obj->Assemble();
- 执行结果:输出 “中国厂商产的_身体部件、日本厂商产的_衣服部件、美国厂商产的_鞋子部件”,满足跨厂商部件的需求
(3)内存释放(关键,避免内存泄漏)
// 释放第一个娃娃及对应资源
delete pbd1obj;
delete pChinaShoes;
delete pChinaClothes;
delete pChinaBody;
delete pChinaFactory;
// 释放第二个娃娃及对应资源
delete pbd2obj;
delete pAmericaShoes;
delete pJapanClothes;
delete pChinaBody2;
delete pAmericaFactory;
delete pJapanFactory;
- 老师特别强调:需按 “娃娃→部件→工厂” 的顺序释放,且可通过
_CrtSetDbgFlag检测内存泄漏(代码开头已配置),确保无内存残留
完整代码:
// MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//公众号:程序员速成 ,内含一辈子都让你感激自己的优质视频教程,欢迎关注
#include <iostream>
#include <vector>
#include <sstream>
#ifdef _DEBUG //只在Debug(调试)模式下
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符
#define new DEBUG_NEW
#endif
#endif
//#include <boost/type_index.hpp>
using namespace std;
//#pragma warning(disable : 4996)
namespace _nmsp1
{
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时析构函数应该为虚函数
protected://可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
//亡灵类怪物
class M_Undead :public Monster
{
public:
//构造函数
M_Undead(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个亡灵类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//元素类怪物
class M_Element :public Monster
{
public:
//构造函数
M_Element(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个元素类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//机械类怪物
class M_Mechanic :public Monster
{
public:
//构造函数
M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个机械类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//野兽类
//class M_Beast :public Monster { .... };
//-------------------------------
/*
//怪物工厂类
class MonsterFactory
{
public:
//Monster* createMonster(string strmontype) //简单工厂模式
static Monster* createMonster(string strmontype) //静态工厂方法模式(Static Factory Method)
{
Monster* prtnobj = nullptr;
if (strmontype == "udd") //udd代表要创建亡灵类怪物
{
prtnobj = new M_Undead(300, 50, 80);
}
else if (strmontype == "elm") //elm代表要创建元素类怪物
{
prtnobj = new M_Element(200, 80, 100);
}
else if (strmontype == "mec") //mec代表要创建机械类怪物
{
prtnobj = new M_Mechanic(400, 0, 110);
}
return prtnobj;
}
};
*/
//所有工厂类的父类
class M_ParFactory
{
public:
virtual Monster* createMonster() = 0; //具体的实现在子类中进行
virtual ~M_ParFactory() {} //做父类时析构函数应该为虚函数
};
//M_Undead怪物类型的工厂,生产M_Undead类型怪物
class M_UndeadFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
//return new M_Undead(300, 50, 80); //创建亡灵类怪物
Monster *ptmp = new M_Undead(300, 50, 80); //创建亡灵类怪物
//这里可以增加一些其他业务代码
//......
return ptmp;
}
};
//M_Element怪物类型的工厂,生产M_Element类型怪物
class M_ElementFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Element(200, 80, 100); //创建元素类怪物
}
};
//M_Mechanic怪物类型的工厂,生产M_Mechanic类型怪物
class M_MechanicFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Mechanic(400, 0, 110); //创建机械类怪物
}
};
//class M_BeastFactory:public M_ParFactory{......};
//全局的用于创建怪物对象的函数,注意形参的类型是工厂父类类型的指针,返回类型是怪物父类类型的指针
Monster* Gbl_CreateMonster(M_ParFactory* factory)
{
return factory->createMonster(); //createMonster虚函数扮演了多态new的行为,factory指向的具体怪物工厂类不同,创建的怪物对象也不同。
}
//-------------------
//不想创建太多工厂类,又想封装变化
//创建怪物工厂子类模板
template <typename T>
class M_ChildFactory :public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new T(300, 50, 80); //如果需要不同的值则可以通过createMonster的形参将值传递进来
}
};
}
namespace _nmsp2
{
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时析构函数应该为虚函数
protected://可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
//沼泽亡灵类怪物
class M_Undead_Swamp :public Monster
{
public:
M_Undead_Swamp(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个沼泽的亡灵类怪物来到了这个世界" << endl;
}
};
//沼泽元素类怪物
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_Undead_Mountain :public Monster
{
public:
M_Undead_Mountain(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个山脉的亡灵类怪物来到了这个世界" << endl;
}
};
//山脉元素类怪物
class M_Element_Mountain :public Monster
{
public:
M_Element_Mountain(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个山脉的元素类怪物来到了这个世界" << endl;
}
};
//山脉机械类怪物
class M_Mechanic_Mountain :public Monster
{
public:
M_Mechanic_Mountain(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个山脉的机械类怪物来到了这个世界" << endl;
}
};
//--------------------------
//城镇亡灵类怪物
class M_Undead_Town :public Monster
{
public:
M_Undead_Town(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 Monster* createMonster_Undead() = 0; //创建亡灵类怪物
virtual Monster* createMonster_Element() = 0; //创建元素类怪物
virtual Monster* createMonster_Mechanic() = 0; //创建机械类怪物
virtual ~M_ParFactory() {} //做父类时析构函数应该为虚函数
};
//沼泽地区的工厂
class M_Factory_Swamp :public M_ParFactory
{
public:
virtual Monster* createMonster_Undead()
{
return new M_Undead_Swamp(300, 50, 120); //创建沼泽亡灵类怪物
}
virtual Monster* createMonster_Element()
{
return new M_Element_Swamp(200, 80, 110); //创建沼泽元素类怪物
}
virtual Monster* createMonster_Mechanic()
{
return new M_Mechanic_Swamp(400, 0, 90); //创建沼泽机械类怪物
}
};
//--------------------------
//山脉地区的工厂
class M_Factory_Mountain :public M_ParFactory
{
public:
virtual Monster* createMonster_Undead()
{
return new M_Undead_Mountain(300, 50, 80); //创建山脉亡灵类怪物
}
virtual Monster* createMonster_Element()
{
return new M_Element_Mountain(200, 80, 100); //创建山脉元素类怪物
}
virtual Monster* createMonster_Mechanic()
{
return new M_Mechanic_Mountain(600, 0, 110); //创建山脉机械类怪物
}
};
//--------------------------
//城镇的工厂
class M_Factory_Town :public M_ParFactory
{
public:
virtual Monster* createMonster_Undead()
{
return new M_Undead_Town(300, 50, 80); //创建城镇亡灵类怪物
}
virtual Monster* createMonster_Element()
{
return new M_Element_Town(200, 80, 100); //创建城镇元素类怪物
}
virtual Monster* createMonster_Mechanic()
{
return new M_Mechanic_Town(400, 0, 110); //创建城镇机械类怪物
}
};
}
namespace _nmsp3
{
//身体抽象类
class Body
{
public:
virtual void getName() = 0;
virtual ~Body() {}
};
//衣服抽象类
class Clothes
{
public:
virtual void getName() = 0;
virtual ~Clothes() {}
};
//鞋子抽象类
class Shoes
{
public:
virtual void getName() = 0;
virtual ~Shoes() {}
};
//---------------------------
//抽象工厂类
class AbstractFactory
{
public:
//所创建的部件应该稳定的保持这三个部件,才适合抽象工厂模式
virtual Body* createBody() = 0; //创建身体
virtual Clothes* createClothes() = 0; //创建衣服
virtual Shoes* createShoes() = 0; //创建鞋子
virtual ~AbstractFactory() {}
};
//---------------------------
//芭比娃娃类
class BarbieDoll
{
public:
//构造函数
BarbieDoll(Body* tmpbody, Clothes* tmpclothes, Shoes* tmpshoes)
{
body = tmpbody;
clothes = tmpclothes;
shoes = tmpshoes;
}
void Assemble() //组装芭比娃娃
{
cout << "成功组装了一个芭比娃娃:" << endl;
body->getName();
clothes->getName();
shoes->getName();
}
private:
Body* body;
Clothes* clothes;
Shoes* shoes;
};
//---------------------------
//中国厂商实现的三个部件
class China_Body :public Body
{
public:
virtual void getName()
{
cout << "中国厂商产的_身体部件" << endl;
}
};
class China_Clothes :public Clothes
{
public:
virtual void getName()
{
cout << "中国厂商产的_衣服部件" << endl;
}
};
class China_Shoes :public Shoes
{
public:
virtual void getName()
{
cout << "中国厂商产的_鞋子部件" << endl;
}
};
//创建一个中国工厂
class ChinaFactory : public AbstractFactory
{
public:
virtual Body* createBody()
{
return new China_Body;
}
virtual Clothes* createClothes()
{
return new China_Clothes;
}
virtual Shoes* createShoes()
{
return new China_Shoes;
}
};
//---------------------------
//日本厂商实现的三个部件
class Japan_Body :public Body
{
public:
virtual void getName()
{
cout << "日本厂商产的_身体部件" << endl;
}
};
class Japan_Clothes :public Clothes
{
public:
virtual void getName()
{
cout << "日本厂商产的_衣服部件" << endl;
}
};
class Japan_Shoes :public Shoes
{
public:
virtual void getName()
{
cout << "日本厂商产的_鞋子部件" << endl;
}
};
//创建一个日本工厂
class JapanFactory : public AbstractFactory
{
public:
virtual Body* createBody()
{
return new Japan_Body;
}
virtual Clothes* createClothes()
{
return new Japan_Clothes;
}
virtual Shoes* createShoes()
{
return new Japan_Shoes;
}
};
//---------------------------
//美国厂商实现的三个部件
class America_Body :public Body
{
public:
virtual void getName()
{
cout << "美国厂商产的_身体部件" << endl;
}
};
class America_Clothes :public Clothes
{
public:
virtual void getName()
{
cout << "美国厂商产的_衣服部件" << endl;
}
};
class America_Shoes :public Shoes
{
public:
virtual void getName()
{
cout << "美国厂商产的_鞋子部件" << endl;
}
};
//创建一个美国工厂
class AmericaFactory : public AbstractFactory
{
public:
virtual Body* createBody()
{
return new America_Body;
}
virtual Clothes* createClothes()
{
return new America_Clothes;
}
virtual Shoes* createShoes()
{
return new America_Shoes;
}
};
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口
//第三章 工厂模式、原型(Prototype)模式、建造者(Builder)模式
//第一节 工厂模式:创建型模式
//new,堆对象,可以实现多态。
//工厂模式:通过把创建对象的代码包装起来,做到创建对象的代码与具体的业务逻辑代码相隔离的目的。
//A *pobja = new A();
//工厂模式细分:a)简单工厂模式;b)工厂方法模式;c)抽象工厂模式。
//面向对象程序设计一个重要原则:开闭原则。
//(1)简单工厂(Simple Factory)模式
//策划:亡灵类怪物,元素类怪物,机械类怪物:都有生命值,魔法值,攻击力三个属性。
//Monster作为父类,M_Undead(亡灵类),M_Element(元素类怪物),M_Mechanic(机械类怪物)。
//使用new +具体类名来创建对象是一种 依赖具体类型的紧耦合关系。
//简单工厂模式的实现思路:使用工厂类可以实现创建怪物的代码 与 各个 具体怪物类对象要实现的逻辑代码隔离。
//封装变化:把依赖范围尽可能缩小,把容易变化的代码段限制在一个小范围内,就可以在很大程度上提高代码的可维护性和可扩展性。
//通过增加新的if else分支来达到支持新怪物增加的目的——违背了面向对象程序设计的原则:开闭原则(Open Close Principle :OCP);
//开闭原则:说的是代码扩展性问题——对扩展开放,对修改关闭(封闭);
//开闭原则详细的解释:当增加新功能,不应该通过修改已经存在的代码来进行,而是应该通过扩展代码(比如增加新类,增加新成员函数)来进行。
//如果if,else分支不多(没有数十上百个),则适当违反开闭原则,是完全可以接受的。
//大家要在代码的可读性和可扩展ing之间做出权衡。
//引入“简单工厂”设计模式的定义(实现意图):定义一个工厂类(MonsterFactory),该类的成员函数(createMonster)可以根据不同参数
//创建并返回不同的类对象,被创建的对象所属的类(M_Undead,M_Element,M_Mechanic)一般都具有相同的父类(Monster),
//调用者(这里指main函数)无需关心创建对象的细节。
//简单工厂方法模式:实现了创建怪物类代码(createMonster),与具体怪物类(M_Undead,M_Element,M_Mechanic)解耦合的效果。
//(2)工厂方法(Factory Method)模式:简称工厂模式或者多态工厂模式。
//与简单工厂模式比,灵活性更强,实现也更加复杂,引入更多的新类。
//M_UndeadFactory,M_ElementFactory,M_MechanicFactory类,有一个共同的父类M_ParFactory(工厂抽象类):
//符合开闭原则,付出的代价是需要新增加多个新的工厂类。
//定义(实现意图):定义一个用于创建对象的接口(M_ParFactory类中的createMonster成员函数,这其实就是个工厂方法,工厂方法模式的名字也是由此而来),
//但由子类(M_UndeadFactory、M_ElementFactory、M_MechanicFactory)决定要实例化的类是哪一个。
//该模式使得某个类(M_Undead、M_Element、M_Mechanic)的实例化延迟到子类(M_UndeadFactory、M_ElementFactory、M_MechanicFactory)。
//出现新怪物类型:M_Beast(野兽类)。
//一般可以认为,将简单工厂模式的代码经过把工厂类进行抽象改造成符合开闭原则后的代码,就变成了工厂方法模式的代码。
//把用new创建对象集中到某个或者某些工厂类的成员函数中去做,的好处:
//a)封装变化。 prtnobj = new M_Element(200,80,100); ->new M_Element(200,80,100,300);
//b)创建对象前需要一些额外的业务代码,可以将这些代码增加到具体的工厂类的createMonster成员函数中。
//简单工厂模式把创建对象这件事放到了一个统一的地方来处理,弹性比较差。而工厂方法模式相当于建立了一个程序实现框架,从而让子类来决定对象如何创建。
//工厂方法模式往往需要创建一个与产品等级结构(层次)相同的工厂等级结构,这也增加了新类的层次结构和数目。
//(3)抽象工厂(Abstract Factory)模式
//(3.1)战斗场景分类范例
//怪物分类:亡灵类,元素类,机械类
//战斗场景分类:沼泽地区,山脉地区,城镇。
//9类怪物====>沼泽地亡灵类、元素类、机械类,山脉地区亡灵类、元素类、机械类,城镇中的亡灵类、元素类、机械类
//工厂方法模式:一个工厂创建一种类怪物。
//但如果一个工厂子类能够创建不止一种而是多种具有相同规则的怪物对象,那么就可以有效的减少所创建的工厂子类数量,这就是抽象工厂模式的核心思想。
//两个概念:a)产品等级结构 b)产品族
//抽象工厂模式是按照产品族来生产产品(产地相同的用一个工厂来生产)——一个地点有一个工厂,该工厂负责生产本产地的所有产品。
//抽象工厂模式优缺点:
//a)增加森林类新场景,怪物种类不变。则只需要增加一个新子工厂比如M_Factory_Forest,符合开闭原则。
//b)增加新怪物种类比如龙类,不但要增加三个继承自Monster的子类,还要针对M_ParFactory增加新的虚函数接口比如createMonster_Dragon,
//同时各个子工厂类都需要实现createMonster_Dragon,这种修改代码的方式来增加新怪物种类,不符合开闭原则,所以增加新怪物的情形不适合抽象工厂模式。
//c)只增加一个产品族则符合开闭原则,只需要增加新工厂子类,这是该模式的优点。
//但若增加新产品等级结构,需要修改抽象层代码,这是抽象工厂模式的缺点,所以,应避免在产品等级结构不稳定的情况下使用该模式。
//也就是说,如果游戏中怪物种类(亡灵类,元素类,机械类)比较固定的情况下,更适合使用抽象工厂模式。
//(3.2) 不同厂商生产不同部件范例
//芭比娃娃:身体(包括头、颈部、躯干、四肢)、衣服、鞋子
//中国,日本,美国 厂商
//要求:制作两个芭比娃娃,第一个:身体,衣服,鞋子,全部采用中国厂商制造的部件。
//第二个:身体采用中国厂商,衣服部件采用日本厂商,鞋子部件采用美国厂商。
//类的设计思路:
//a)将身体,衣服,鞋子 这三个部件实现为抽象类。
//b)实现一个抽象工厂,分别用来生产身体、衣服、鞋子这三个部件。
//c)针对不同厂商的每个部件实现具体的类以及每个厂商所代表的具体工厂。
//工厂方法模式和抽象工厂模式区别:
//a)工厂方法模式:一个工厂生产一个产品
//b)抽象工厂模式:一个工厂生产多个产品(产品族)
//抽象工厂模式的定义(实现意图):提供一个接口(AbstractFactory),
//让该接口负责创建一系列相关或者相互依赖的对象(Body,Clothes,Shoes),而无需指定他们具体的类。
//三种工厂模式的总结:
//a)从代码实现复杂度:
//b)从需要的工厂数量上:
//c)从实际应用上:
/*
_nmsp1::Monster* pM1 = new _nmsp1::M_Undead(300, 50, 80); //产生一只亡灵类怪物
_nmsp1::Monster* pM2 = new _nmsp1::M_Element(200, 80, 100); //产生一只元素类怪物
_nmsp1::Monster* pM3 = new _nmsp1::M_Mechanic(400, 0, 110); //产生一只机械类怪物
//pM2->attack
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
/*_nmsp1::MonsterFactory facobj;
_nmsp1::Monster* pM1 = facobj.createMonster("udd"); //产生了一只亡灵类怪物,当然这里必须知道“udd”代表的是创建亡灵类怪物
_nmsp1::Monster* pM2 = facobj.createMonster("elm"); //创建一只元素类怪物
_nmsp1::Monster* pM3 = facobj.createMonster("mec"); //创建一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
/*
//此时简单工厂模式又可以称为静态工厂方法模式(Static Factory Method)
_nmsp1::Monster* pM1 = _nmsp1::MonsterFactory::createMonster("udd"); //产生了一只亡灵类怪物,当然这里必须知道“udd”代表的是创建亡灵类怪物
_nmsp1::Monster* pM2 = _nmsp1::MonsterFactory::createMonster("elm"); //创建一只元素类怪物
_nmsp1::Monster* pM3 = _nmsp1::MonsterFactory::createMonster("mec"); //创建一只机械类怪物
//释放资源
delete pM1;
delete pM2;
delete pM3;
*/
/*
_nmsp1::M_ParFactory* p_ud_fy = new _nmsp1::M_UndeadFactory(); //多态工厂,注意指针类型
_nmsp1::Monster* pM1 = _nmsp1::Gbl_CreateMonster(p_ud_fy); //产生了一只亡灵类怪物,也是多态,注意返回类型
//当然,这里也可以直接写成 Monster *pM1 = p_ud_fy->createMonster();
_nmsp1::M_ParFactory* p_elm_fy = new _nmsp1::M_ElementFactory();
_nmsp1::Monster *pM2 = _nmsp1::Gbl_CreateMonster(p_elm_fy); //产生了一只元素类怪物
_nmsp1::M_ParFactory* p_mec_fy = new _nmsp1::M_MechanicFactory();
_nmsp1::Monster* pM3 = _nmsp1::Gbl_CreateMonster(p_mec_fy); //产生了一只机械类怪物
//释放资源
//释放工厂
delete p_ud_fy;
delete p_elm_fy;
delete p_mec_fy;
//释放怪物
delete pM1;
delete pM2;
delete pM3;
*/
/*
_nmsp1::M_ChildFactory<_nmsp1::M_Undead> myFactory;
_nmsp1::Monster* pM10 = myFactory.createMonster();
//释放资源
delete pM10;
*/
/*
_nmsp2::M_ParFactory* p_mou_fy = new _nmsp2::M_Factory_Mountain(); //多态工厂,山脉地区的工厂
_nmsp2::Monster* pM1 = p_mou_fy->createMonster_Element(); //创建山脉地区的元素类怪物
_nmsp2::M_ParFactory* p_twn_fy = new _nmsp2::M_Factory_Town(); //多态工厂,城镇的工厂
_nmsp2::Monster* pM2 = p_twn_fy->createMonster_Undead(); //创建城镇地区的亡灵类怪物
_nmsp2::Monster* pM3 = p_twn_fy->createMonster_Mechanic(); //创建城镇地区的机械类怪物
//释放资源
//释放工厂
delete p_mou_fy;
delete p_twn_fy;
delete pM1;
delete pM2;
delete pM3;
*/
//创建第一个芭比娃娃------------
//(1)创建一个中国工厂
_nmsp3::AbstractFactory* pChinaFactory = new _nmsp3::ChinaFactory();
//(2)创建中国产的各种部件
_nmsp3::Body* pChinaBody = pChinaFactory->createBody();
_nmsp3::Clothes* pChinaClothes = pChinaFactory->createClothes();
_nmsp3::Shoes* pChinaShoes = pChinaFactory->createShoes();
//(3)创建芭比娃娃
_nmsp3::BarbieDoll* pbd1obj = new _nmsp3::BarbieDoll(pChinaBody, pChinaClothes, pChinaShoes);
pbd1obj->Assemble(); //组装芭比娃娃
cout << "-------------------------------------" << endl;
//创建第二个芭比娃娃------------
//(1)创建另外两个工厂:日本工厂,美国工厂
_nmsp3::AbstractFactory* pJapanFactory = new _nmsp3::JapanFactory();
_nmsp3::AbstractFactory* pAmericaFactory = new _nmsp3::AmericaFactory();
//(2)创建中国产的身体部件,日本产的衣服部件,美国产的鞋子部件
_nmsp3::Body* pChinaBody2 = pChinaFactory->createBody();
_nmsp3::Clothes* pJapanClothes = pJapanFactory->createClothes();
_nmsp3::Shoes* pAmericaShoes = pAmericaFactory->createShoes();
//(3)创建芭比娃娃
_nmsp3::BarbieDoll* pbd2obj = new _nmsp3::BarbieDoll(pChinaBody2, pJapanClothes, pAmericaShoes);
pbd2obj->Assemble(); //组装芭比娃娃
//最后记得释放内存----------------
delete pbd1obj;
delete pChinaShoes;
delete pChinaClothes;
delete pChinaBody;
delete pChinaFactory;
//------------
delete pbd2obj;
delete pAmericaShoes;
delete pJapanClothes;
delete pChinaBody2;
delete pAmericaFactory;
delete pJapanFactory;
return 0;
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门使用技巧:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
1.3.6核心模式对比(工厂方法 vs 抽象工厂)
| 维度 | 工厂方法模式 | 抽象工厂模式 |
|---|---|---|
| 工厂生产能力 | 一个工厂仅生产一种产品 | 一个工厂生产多个产品(产品族) |
| 类数量 | 工厂类数量 = 产品数量 | 工厂类数量 = 产品族数量(数量更少) |
| 适用场景 | 单一产品的多类型扩展 | 多产品族的成套生产 |
| 本案例对应 | 若为每个部件单独建工厂(如中国身体工厂、中国衣服工厂),则为工厂方法 | 一个中国工厂生产全套中国部件,为抽象工厂 |
1.3.7抽象工厂模式的定义(结合本案例)
提供一个接口(AbstractFactory),让该接口负责创建一系列相关或相互依赖的对象(身体、衣服、鞋子),而无需指定它们的具体类(调用者仅依赖抽象类,不关心是中国 / 日本 / 美国部件)
1.3.8三种工厂模式整体总结
(1)代码实现复杂度
简单工厂模式 < 工厂方法模式 < 抽象工厂模式
- 简单工厂:仅需 1 个工厂类,通过
if-else分支生产产品,实现最简单 - 工厂方法:需为每个产品建工厂,类数量翻倍,复杂度提升
- 抽象工厂:需同时抽象产品和工厂,还要实现多产品生产,复杂度最高
(2)工厂数量
简单工厂模式(1 个) < 抽象工厂模式(产品族数量) < 工厂方法模式(产品数量)
- 工厂方法可视为抽象工厂的特例:若抽象工厂仅生产 1 种产品,则退化为工厂方法
(3)实际应用场景
- 简单工厂:产品类型少且不常变更的小型项目(如 3 种以内怪物的创建),可接受轻微违背开闭原则
- 工厂方法:产品类型较多、需严格遵循开闭原则的中型项目(如每种怪物单独建工厂)
- 抽象工厂:存在多产品族、产品等级结构稳定的大型项目(如本案例的多厂商多部件生产、游戏多场景多怪物生产)
第二节 原型(Prototype)模式
2.1通过工厂方法模式演变到原型模式
2.1.1核心概念:什么是原型模式?
- 定义:原型模式是一种创建型模式。它通过一个现有的对象(即“原型”或“种子”),通过克隆(Clone)的方式来创建出多个一模一样的新对象。
- 生活类比:就像克隆羊,有一个原始的母体(原型),通过它可以克隆出一堆一模一样的羊。
- 适用场景:
- 创建过程复杂:当创建一个对象非常繁琐(例如需要设置大量的成员变量、属性值)时,直接克隆现有对象比重新
new并逐个赋值要高效且容易得多。 - 保留状态:当需要复制一个对象当前的具体状态(例如游戏中一个怪物被砍了一刀,剩下300血,克隆出来的怪物也应该是300血,而不是满血)时。
- 创建过程复杂:当创建一个对象非常繁琐(例如需要设置大量的成员变量、属性值)时,直接克隆现有对象比重新
2.1.2工厂方法模式
namespace _nmsp1
{
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时析构函数应该为虚函数
protected://可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
//亡灵类怪物
class M_Undead :public Monster
{
public:
//构造函数
M_Undead(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个亡灵类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//元素类怪物
class M_Element :public Monster
{
public:
//构造函数
M_Element(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个元素类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//机械类怪物
class M_Mechanic :public Monster
{
public:
//构造函数
M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个机械类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//野兽类
//class M_Beast :public Monster { .... };
//-------------------------------
/*
//怪物工厂类
class MonsterFactory
{
public:
//Monster* createMonster(string strmontype) //简单工厂模式
static Monster* createMonster(string strmontype) //静态工厂方法模式(Static Factory Method)
{
Monster* prtnobj = nullptr;
if (strmontype == "udd") //udd代表要创建亡灵类怪物
{
prtnobj = new M_Undead(300, 50, 80);
}
else if (strmontype == "elm") //elm代表要创建元素类怪物
{
prtnobj = new M_Element(200, 80, 100);
}
else if (strmontype == "mec") //mec代表要创建机械类怪物
{
prtnobj = new M_Mechanic(400, 0, 110);
}
return prtnobj;
}
};
*/
//所有工厂类的父类
class M_ParFactory
{
public:
virtual Monster* createMonster() = 0; //具体的实现在子类中进行
virtual ~M_ParFactory() {} //做父类时析构函数应该为虚函数
};
//M_Undead怪物类型的工厂,生产M_Undead类型怪物
class M_UndeadFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
//return new M_Undead(300, 50, 80); //创建亡灵类怪物
Monster *ptmp = new M_Undead(300, 50, 80); //创建亡灵类怪物
//这里可以增加一些其他业务代码
//......
return ptmp;
}
};
//M_Element怪物类型的工厂,生产M_Element类型怪物
class M_ElementFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Element(200, 80, 100); //创建元素类怪物
}
};
//M_Mechanic怪物类型的工厂,生产M_Mechanic类型怪物
class M_MechanicFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Mechanic(400, 0, 110); //创建机械类怪物
}
};
//class M_BeastFactory:public M_ParFactory{......};
//全局的用于创建怪物对象的函数,注意形参的类型是工厂父类类型的指针,返回类型是怪物父类类型的指针
Monster* Gbl_CreateMonster(M_ParFactory* factory)
{
return factory->createMonster(); //createMonster虚函数扮演了多态new的行为,factory指向的具体怪物工厂类不同,创建的怪物对象也不同。
}
//-------------------
//不想创建太多工厂类,又想封装变化
//创建怪物工厂子类模板
template <typename T>
class M_ChildFactory :public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new T(300, 50, 80); //如果需要不同的值则可以通过createMonster的形参将值传递进来
}
};
}
(1)工厂方法模式的结构
工厂方法模式通过「工厂类 + 产品类」的双层继承结构实现对象创建,核心是 “工厂负责生产产品”:
| 类层次 | 具体类 | 职责 |
|---|---|---|
| 产品父类 | Monster |
定义怪物核心属性(生命值、魔法值、攻击力) |
| 产品子类 | M_Undead(亡灵)、M_Element(元素)、M_Mechanic(机械) |
实现具体怪物的构造(初始化属性 + 打印提示) |
| 工厂父类 | M_ParFactory |
声明纯虚函数createMonster()(创建怪物的接口) |
| 工厂子类 | M_UndeadFactory、M_ElementFactory等 |
重写createMonster(),创建对应类型的怪物对象 |
(2)工厂方法模式的核心代码片段
// 工厂子类示例:机械类怪物工厂
class M_MechanicFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Mechanic(400, 0, 110); // 固定属性创建新对象
}
};
(3)工厂方法模式的局限
- 类数量冗余:每增加一个产品子类,必须对应增加一个工厂子类
- 无法复用已有对象的状态:创建的对象都是 “全新初始化” 的,无法基于现有对象的修改后状态生成新对象(比如怪物掉血后无法克隆当前残血状态)
2.1.3核心演变:从工厂方法模式到原型模式
原型模式的核心思想是「产品自己具备创建自己的能力」,无需单独工厂类。演变步骤围绕 “合并类职责、简化结构” 展开,最终代码参考_nmsp2。
步骤 1:合并工厂父类与产品父类,转移创建能力
- 原逻辑:工厂父类
M_ParFactory的核心能力是createMonster()(创建怪物) - 演变操作:
- 删除工厂父类
M_ParFactory - 将
createMonster()接口移到产品父类Monster中 - 保留
Monster的核心属性(m_life、m_magic、m_attack)和虚析构函数
- 删除工厂父类
步骤 2:统一接口命名,符合原型模式规范
- 原接口
createMonster()语义是 “创建新对象”,原型模式强调 “克隆现有对象” - 演变操作:将
Monster父类中的createMonster()重命名为clone()(语义更贴合 “克隆”),并声明为纯虚函数:
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时虚构函数应该为虚函数
public:
virtual Monster* clone() = 0; //具体的实现在子类中进行
protected: //可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
步骤 3:转移工厂子类的创建逻辑到产品子类,删除工厂子类
- 原逻辑:工厂子类通过
createMonster()创建对应产品子类对象(固定属性) - 演变操作:
- 删除所有工厂子类(
M_UndeadFactory、M_ElementFactory等) - 将工厂子类中
createMonster()的逻辑移到对应产品子类中,并重命名为clone() - 关键修改:
clone()不再创建 “固定属性的新对象”,而是基于当前对象状态克隆(通过拷贝构造函数实现)
- 删除所有工厂子类(
演变前后结构对比
| 工厂方法模式 | 原型模式 |
|---|---|
| 产品父类 + 工厂父类 | 仅产品父类(集成克隆能力) |
| 产品子类 + 工厂子类 | 仅产品子类(实现克隆逻辑) |
| 工厂创建对象 | 对象自身克隆自己 |
| 固定属性创建 | 基于当前状态创建 |
2.1.4原型模式的关键技术:拷贝构造函数
(1)克隆的本质:调用拷贝构造函数
原型模式的clone()方法核心是 “复制当前对象的所有状态”,而 C++ 中复制对象的基础是拷贝构造函数:
- 若程序员未显式定义拷贝构造函数,编译器会生成默认拷贝构造函数(浅拷贝)
- 若对象包含指针、动态内存等资源,必须显式定义拷贝构造函数(深拷贝),否则会导致内存泄漏或访问异常
如果类中没有定义拷贝构造函数,编译器会合成一个默认的。但在实际项目中,必须警惕深浅拷贝问题。
- 浅拷贝(Shallow Copy):只复制指针的地址。如果原对象销毁,新对象的指针就会指向非法内存(悬空指针),或者两个对象修改同一块内存,导致逻辑错误。
- 深拷贝(Deep Copy):不仅复制指针,还为指针指向的内容重新分配内存并复制数据。
- 结论:在原型模式中,为了保证克隆出的对象是完全独立的全新个体,涉及到指针成员变量时,必须手动实现拷贝构造函数以执行深拷贝。
| 浅拷贝(默认) | 深拷贝(显式实现) |
|---|---|
| 仅复制成员变量的 “值” | 复制值 + 为指针 / 动态资源分配新内存 |
| 指针成员指向同一内存 | 指针成员指向不同内存 |
| 可能导致重复释放、内存泄漏 | 避免上述问题,原型模式必需 |
(2)显式拷贝构造函数实现(代码示例)
以M_Element(元素类怪物)和M_Mechanic(机械类怪物)为例:
// 元素类怪物的拷贝构造函数
class M_Element : public Monster
{
public:
// 普通构造函数(初始化父类属性+打印提示)
M_Element(int life, int magic, int attack) : Monster(life, magic, attack)
{
cout << "一只元素类怪物来到了这个世界" << endl;
}
// 显式拷贝构造函数(深拷贝示例)
M_Element(const M_Element& tmpobj) : Monster(tmpobj) // 调用父类拷贝构造初始化父类属性
{
cout << "调用拷贝构造函数创建了一只元素类怪物" << endl;
// 若有指针成员,需在此处分配新内存并复制内容(深拷贝)
// 示例:若有char* name,则需 new char[strlen(tmpobj.name)+1]; strcpy(name, tmpobj.name);
}
// 克隆方法实现:通过拷贝构造函数复制当前对象
virtual Monster* clone()
{
return new M_Element(*this); // *this是当前对象,触发拷贝构造
}
};
- 关键说明:
new M_Element(*this)中,*this表示当前对象的引用,传递给拷贝构造函数,创建一个与当前对象状态完全一致的新对象
2.1.5原型模式完整代码详解
完整代码:
// MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//公众号:程序员速成 ,内含一辈子都让你感激自己的优质视频教程,欢迎关注
#include <iostream>
#ifdef _DEBUG //只在Debug(调试)模式下
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符
#define new DEBUG_NEW
#endif
#endif
//#include <boost/type_index.hpp>
using namespace std;
//#pragma warning(disable : 4996)
namespace _nmsp1
{
//工厂方法模式(代码放到这里,方便比较)----------------------------------------
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时析构函数应该为虚函数
protected://可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
//亡灵类怪物
class M_Undead :public Monster
{
public:
//构造函数
M_Undead(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个亡灵类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//元素类怪物
class M_Element :public Monster
{
public:
//构造函数
M_Element(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个元素类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//机械类怪物
class M_Mechanic :public Monster
{
public:
//构造函数
M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一个机械类怪物来到了这个世界" << endl;
}
//其他代码略......
};
//所有工厂类的父类
class M_ParFactory
{
public:
virtual Monster* createMonster() = 0; //具体的实现在子类中进行
virtual ~M_ParFactory() {} //做父类时析构函数应该为虚函数
};
//M_Undead怪物类型的工厂,生产M_Undead类型怪物
class M_UndeadFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
//return new M_Undead(300, 50, 80); //创建亡灵类怪物
Monster* ptmp = new M_Undead(300, 50, 80); //创建亡灵类怪物
//这里可以增加一些其他业务代码
//......
return ptmp;
}
};
//M_Element怪物类型的工厂,生产M_Element类型怪物
class M_ElementFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Element(200, 80, 100); //创建元素类怪物
}
};
//M_Mechanic怪物类型的工厂,生产M_Mechanic类型怪物
class M_MechanicFactory : public M_ParFactory
{
public:
virtual Monster* createMonster()
{
return new M_Mechanic(400, 0, 110); //创建机械类怪物
}
};
}
namespace _nmsp2
{
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时虚构函数应该为虚函数
public:
virtual Monster* clone() = 0; //具体的实现在子类中进行
protected: //可能被子类访问的成员,所以用protected修饰
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
//亡灵类怪物
class M_Undead :public Monster
{
public:
//构造函数
M_Undead(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一只亡灵类怪物来到了这个世界" << endl;
}
//拷贝构造函数
//..........留给大家自己写
virtual Monster* clone()
{
//return new M_Undead(300, 50, 80); //创建亡灵类怪物
return new M_Undead(*this); //触发拷贝构造函数的调用来创建亡灵类怪物
}
//...其他代码略
};
//元素类怪物
class M_Element :public Monster
{
public:
//构造函数
M_Element(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一只元素类怪物来到了这个世界" << endl;
}
//拷贝构造函数
M_Element(const M_Element& tmpobj) :Monster(tmpobj) //初始化列表中注意对父类子对象的初始化
{
cout << "调用了M_Element::M_Element(const M_Element& tmpobj)拷贝构造函数创建了一只元素类怪物" << endl;
}
virtual Monster* clone()
{
//return new M_Element(200, 80, 100); //创建元素类怪物
return new M_Element(*this);
}
//...其他代码略
};
//机械类怪物
class M_Mechanic :public Monster
{
public:
//构造函数
M_Mechanic(int life, int magic, int attack) :Monster(life, magic, attack)
{
cout << "一只机械类怪物来到了这个世界" << endl;
}
//拷贝构造函数
M_Mechanic(const M_Mechanic& tmpobj) :Monster(tmpobj) //初始化列表中注意对父类子对象的初始化
{
cout << "调用了M_Mechanic::M_Mechanic(const M_Mechanic& tmpobj)拷贝构造函数创建了一只机械类怪物" << endl;
}
virtual Monster* clone()
{
//return new M_Mechanic(400, 0, 110); //创建机械类怪物
return new M_Mechanic(*this);
}
//...其他代码略
};
}
int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口
//第二节 原型(Prototype)模式
//原型模式:通过一个对象(原型对象)克隆出多个一模一样的对象。
//(1)通过工厂方法模式演变到原型模式
//克隆对象自身实际上是需要调用类的拷贝构造函数。《C++新经典:对象模型》:深拷贝,浅拷贝
//克隆对象意味着复制出一个全新的对象,所以设计到深浅拷贝时都要实现深拷贝
_nmsp2::M_Mechanic myPropMecMonst(400, 0, 110); //创建一只机械类怪物对象作为原型对象以用于克隆目的
_nmsp2::Monster* pmyPropEleMonster = new _nmsp2::M_Element(200, 80, 100); //创建一只元素类怪物对象作为原型对象以用于克隆目的,
//这里可以直接new,也可以通过工厂模式创建原型对象,取决于程序员自己的洗好。
//.....
_nmsp2::Monster* p_CloneObj1 = myPropMecMonst.clone(); //使用原型对象克隆出新的机械类怪物对象
_nmsp2::Monster* p_CloneObj2 = pmyPropEleMonster->clone(); //使用原型对象克隆出新的元素类怪物对象
//可以对p_CloneObj1、p_CloneObj2所指向的对象进行各种操作(实现具体的业务逻辑)
//......
//释放资源
//释放克隆出来的怪物对象
delete p_CloneObj1;
delete p_CloneObj2;
//释放原型对象(堆中的)
delete pmyPropEleMonster;
return 0;
}
(1)产品父类Monster
class Monster
{
public:
// 构造函数:初始化怪物核心属性
Monster(int life, int magic, int attack) : m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} // 虚析构:确保子类对象正确释放
virtual Monster* clone() = 0; // 克隆接口(纯虚函数,强制子类实现)
protected:
int m_life; // 生命值
int m_magic; // 魔法值
int m_attack; // 攻击力
};
(2)产品子类(3 个怪物类型)
- 共性:继承
Monster,实现clone(),显式拷贝构造函数(机械、元素类) - 差异:构造函数的提示信息和初始属性,
clone()中调用自身拷贝构造函数
(3)测试代码(main函数)
int main()
{
// 1. 创建原型对象(种子对象,用于克隆)
_nmsp2::M_Mechanic myPropMecMonst(400, 0, 110); // 栈上原型对象(机械类,初始400血)
_nmsp2::Monster* pmyPropEleMonster = new _nmsp2::M_Element(200, 80, 100); // 堆上原型对象(元素类)
// 2. 基于原型对象克隆新对象
_nmsp2::Monster* p_CloneObj1 = myPropMecMonst.clone(); // 克隆机械类怪物(继承原型400血状态)
_nmsp2::Monster* p_CloneObj2 = pmyPropEleMonster->clone(); // 克隆元素类怪物
// 3. 业务逻辑:对克隆对象进行操作(如掉血、攻击等,不影响原型对象)
// 示例:p_CloneObj1->m_life -= 100; (克隆对象掉血,原型对象仍为400血)
// 4. 释放资源(避免内存泄漏)
delete p_CloneObj1; // 释放克隆对象
delete p_CloneObj2; // 释放克隆对象
delete pmyPropEleMonster; // 释放堆上原型对象
return 0;
}
(4)运行结果与说明
// 原型对象创建输出
一只机械类怪物来到了这个世界
一只元素类怪物来到了这个世界
// 克隆对象创建输出(触发拷贝构造函数)
调用了M_Mechanic::拷贝构造函数创建了一只机械类怪物
调用了M_Element::拷贝构造函数创建了一只元素类怪物
- 克隆对象与原型对象是完全独立的内存空间(地址不同)
- 克隆对象的初始状态与原型对象一致,后续修改互不影响
2.1.6原型模式的应用场景与优势
(1)适用场景
- 对象创建流程复杂(如属性多、初始化步骤繁琐),克隆比重新初始化更高效
- 需要基于现有对象的状态生成新对象(如游戏中残血 BOSS 克隆自己)
- 避免工厂方法模式中 “工厂类冗余” 的问题
(2)核心优势
- 简化对象创建:无需单独工厂类,对象自身即可克隆
- 复用对象状态:基于现有对象的修改后状态快速生成新对象
- 扩展性好:新增产品子类时,只需实现
clone()和拷贝构造函数,无需修改其他代码
(3) 实际项目扩展建议
- 原型对象管理:可将多个原型对象存入
map容器(如map<string, Monster*>),通过 key 快速获取并克隆 - 深拷贝封装:若多个类需要深拷贝,可封装深拷贝工具函数,避免重复代码
- 结合工厂模式:原型对象的创建可结合工厂模式(如通过工厂创建初始原型,再克隆)
2.1.7本节课重点总结
- 演变逻辑:工厂方法模式(工厂创建对象)→ 原型模式(对象克隆自己),核心是 “转移创建职责”
- 原型模式定义:通过原型对象克隆生成多个一模一样的对象,属于创建型模式
- 关键技术:拷贝构造函数是克隆的基础,涉及动态资源必须实现深拷贝
- 代码核心:父类声明
clone()纯虚函数,子类实现clone()并调用自身拷贝构造函数 - 核心差异:工厂方法模式创建 “固定初始状态” 对象,原型模式创建 “当前状态” 对象
2.2引入原型(Prototype)模式
2.2.1原型模式的引入背景
(1)从拷贝构造函数到 clone 方法的必要性
上节课通过工厂方法模式演变到原型模式的核心是 “对象克隆”,而克隆的本质是调用类的拷贝构造函数。但为什么不直接使用拷贝构造函数,而是要封装clone成员函数?
- 设计模式的核心特性是跨语言通用性:C++ 中有拷贝构造函数,但 Java、C# 等语言没有这一概念,此时克隆逻辑必须封装在
clone方法中,保证模式的通用性。 - 代码规范性:
clone作为统一的克隆接口,让所有原型类遵循相同的调用标准,避免直接使用拷贝构造函数导致的接口混乱。
(2)代码衔接(上节课代码修正)
在本节课代码(_nmsp2命名空间)中,为Monster类及子类新增了clone方法,奠定原型模式的核心接口。
2.2.2原型模式的核心定义
官方定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新对象。
通俗理解:以一个已存在的对象(原型实例)为模板,通过 “克隆”(拷贝)的方式快速创建出与原型对象类型相同、状态一致的新对象,核心是 “复制现有对象,而非从零创建”。
代码对应示例
// 原型实例:创建一个元素类怪物作为原型
_nmsp2::Monster* pMonsterObj = new _nmsp2::M_Element(200, 80, 100);
// 克隆新对象:通过原型实例的clone方法,创建相同类型、相同初始状态的新对象
_nmsp2::Monster* pCloneObj = pMonsterObj->clone();
- 原型实例:
pMonsterObj指向的M_Element对象(包含m_life=200、m_magic=80、m_attack=100)。 - 克隆结果:
pCloneObj指向一个全新的M_Element对象,其成员变量与原型实例完全一致。
2.2.3原型模式的核心角色(UML 对应代码结构)
原型模式包含两个核心角色,对应代码中的类层级关系:
| 角色名称 | 定义与职责 | 代码对应类 |
|---|---|---|
| Prototype(抽象原型类) | 所有具体原型类的父类,声明克隆方法(纯虚函数),统一克隆接口。 | _nmsp2::Monster类 |
| ConcretePrototype(具体原型类) | 继承抽象原型类,实现克隆方法,返回自身的拷贝对象,完成具体的克隆逻辑。 | M_Undead、M_Element、M_Mechanic类 |
代码角色解析
1.抽象原型类(Monster):
//怪物父类
class Monster
{
public:
//构造函数
Monster(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} //做父类时虚构函数应该为虚函数
public:
virtual Monster* clone() = 0; //具体的实现在子类中进行
protected: //可能被子类访问的成员,所以用protected修饰
//public:
//怪物属性
int m_life; //生命值
int m_magic; //魔法值
int m_attack; //攻击力
};
- 核心作用:定义克隆的统一接口,让客户端无需关心具体原型类,只需调用
clone即可克隆对象。
2.具体原型类(以M_Element为例):
class M_Element : public Monster
{
public:
// 拷贝构造函数:克隆的核心底层逻辑(复制成员变量)
M_Element(const M_Element& tmpobj) : Monster(tmpobj)
{
cout << "调用拷贝构造函数克隆元素类怪物" << endl;
}
// 实现clone方法:通过拷贝构造函数创建自身拷贝
virtual Monster* clone()
{
return new M_Element(*this); // *this是当前原型实例,触发拷贝构造
}
};
- 核心作用:通过
clone方法封装克隆逻辑,返回与当前实例类型相同、状态一致的新对象。
2.2.4原型模式的适用场景(对比工厂方法模式)
老师强调:原型模式的核心优势在于 “保留对象当前状态” 和 “简化复杂对象创建”,具体适用场景为:
当对象满足以下两个条件时,优先使用原型模式而非工厂方法模式:
- 对象内部数据复杂且多变(如游戏怪物的生命值、魔法值、状态 buff(中毒、混乱)等动态变化的数据);
- 创建对象时需要保持其当前状态(如怪物残血时克隆分身,分身需继承残血状态)。
场景对比:工厂方法 vs 原型模式
(1)工厂方法模式的局限(代码见_nmsp1)
若用工厂方法创建上述复杂对象,步骤繁琐:
// 工厂方法创建怪物(需手动设置所有状态)
Monster* pMonster = new M_Element(200, 80, 100); // 初始状态
pMonster->setLife(50); // 模拟残血状态
pMonster->setMagic(20); // 模拟低魔法值
// 若要创建相同状态的分身,需重复调用set接口:
Monster* pClone = new M_Element(200, 80, 100);
pClone->setLife(50);
pClone->setMagic(20);
- 问题:需手动复制所有状态,代码冗余,且易遗漏状态数据。
(2)原型模式的优势(代码见_nmsp2)
克隆时直接保留当前状态,无需手动设置:
// 原型实例(已处于残血状态)
M_Element* pProto = new M_Element(200, 80, 100);
pProto->setLife(50);
pProto->setMagic(20);
// 克隆分身:直接继承原型的残血状态
Monster* pClone = pProto->clone(); // 无需额外设置,m_life=50、m_magic=20
- 核心:
clone通过拷贝构造函数,一次性复制原型的所有成员变量,保留当前状态。
2.2.5原型模式与工厂方法模式的异同点
(1)相同点
- 均遵循 “封装创建逻辑” 原则,客户端无需知道具体对象的类名(工厂方法通过工厂类隐藏,原型模式通过
clone接口隐藏)。- 工厂方法:
factory->createMonster()(无需知道M_Element); - 原型模式:
proto->clone()(无需知道M_Element)。
- 工厂方法:
(2)不同点
| 对比维度 | 工厂方法模式(_nmsp1) |
原型模式(_nmsp2) |
|---|---|---|
| 创建依据 | 根据类名创建新对象(new M_Element(...)) |
根据现有对象创建新对象(new M_Element(*this)) |
| 状态保留 | 无法直接保留对象状态,需手动设置 | 自动保留原型对象的当前状态 |
| 额外结构 | 需要与产品类层级对应的工厂类层级(如M_ParFactory及其子类) |
无额外工厂类,仅需原型类自身实现clone方法 |
| 核心逻辑 | 实例化新对象(从零创建) | 拷贝现有对象(复制创建) |
(3)特殊关系
原型模式可看作特殊的工厂方法模式:
- 工厂:具体原型类本身(如
M_Element类); - 工厂方法:
clone方法(负责 “生产” 自身的拷贝对象)。
2.2.6原型模式的优缺点
(1)优点
①创建复杂对象效率高
- 若对象的成员变量是通过复杂计算(排序、哈希)、IO 操作(读文件、数据库查询)得到的,克隆可避免重复执行这些耗时操作,直接复制结果。
- 示例:若
m_life是通过战斗算法实时计算的残血值,克隆时无需重新计算,直接复制。
- 示例:若
②无额外工厂类开销
- 对比工厂方法模式(需创建
M_ParFactory、M_ElementFactory等多个工厂类),原型模式无需额外工厂类,减少代码冗余和内存开销。
③简化代码,灵活扩展
-
如全局函数
Gbl_CreateMonster2的对比:-
无
clone方法时(旧版本):需通过dynamic_cast判断类型,代码繁琐且需感知所有子类;// 旧版本:需判断所有具体原型类,扩展性差 void Gbl_CreateMonster2(Monster* pMonster) { if (dynamic_cast<M_Element*>(pMonster) != nullptr) { ptmpobj = new M_Element(200,80,100); } else if (dynamic_cast<M_Undead*>(pMonster) != nullptr) { ptmpobj = new M_Undead(300,50,80); } } -
有
clone方法时(新版本):无需判断类型,直接克隆,扩展性强(新增子类无需修改此函数)。// 新版本:依赖clone接口,简洁且易扩展 void Gbl_CreateMonster2(Monster* pMonster) { Monster* ptmpobj = pMonster->clone(); // 无需感知具体子类 }
-
④克隆方法实现灵活
克隆方法的实现不止一种,可根据需求选择:
-
方式 1:调用拷贝构造函数(推荐,简洁高效):
virtual Monster* clone() { return new M_Element(*this); } -
方式 2:通过
set方法赋值(适用于无拷贝构造函数的场景):virtual Monster* clone() { M_Element* pNew = new M_Element(0,0,0); pNew->setLife(this->m_life); // 需为成员变量提供set接口 pNew->setMagic(this->m_magic); return pNew; }
(2)缺点
①增加开发者负担
所有具体原型类都需实现clone方法,若子类较多,开发和维护成本增加。
②需注意成员变量访问权限
- 若通过
set方法实现克隆,需为protected成员变量提供set接口(如setLife); - 若直接访问成员变量(如
pNew->m_life = this->m_life),需将成员变量改为public,破坏封装性,不推荐。
③需处理深浅拷贝问题
- 若成员变量包含指针、引用等复合类型,默认拷贝构造函数是 “浅拷贝”(仅复制指针地址,不复制指向的内容),会导致原型与克隆对象共享资源;
- 解决方案:必须手动实现深拷贝(复制指针指向的内容),确保克隆对象是完全独立的。
2.2.7核心代码完整解析(_nmsp2命名空间)
(1)抽象原型类(Monster)
class Monster
{
public:
Monster(int life, int magic, int attack) : m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Monster() {} // 虚析构,确保子类对象正确释放
virtual Monster* clone() = 0; // 纯虚克隆接口,强制子类实现
protected:
int m_life; // 生命值(状态数据)
int m_magic; // 魔法值(状态数据)
int m_attack; // 攻击力(状态数据)
};
- 核心:
clone纯虚函数定义统一接口,protected成员变量保证封装性,同时允许子类访问。
(2)具体原型类(M_Element)
class M_Element : public Monster
{
public:
// 普通构造函数:创建原型实例
M_Element(int life, int magic, int attack) : Monster(life, magic, attack)
{
cout << "一个元素类怪物来到了这个世界" << endl;
}
// 拷贝构造函数:克隆的底层实现
M_Element(const M_Element& tmpobj) : Monster(tmpobj)
{
cout << "调用拷贝构造函数克隆元素类怪物" << endl;
}
// 实现克隆接口:返回自身拷贝
virtual Monster* clone()
{
return new M_Element(*this); // *this是当前原型实例,触发拷贝构造
}
};
- 拷贝构造函数:通过
Monster(tmpobj)调用父类拷贝构造,复制m_life、m_magic、m_attack; clone方法:封装克隆逻辑,返回新的M_Element对象。
(3)客户端调用示例(main函数)
int main()
{
// 1. 创建原型实例(堆上对象)
_nmsp2::Monster* pProto = new _nmsp2::M_Element(200, 80, 100);
// 2. 克隆新对象:保留原型的初始状态
_nmsp2::Monster* pClone = pProto->clone();
// 3. 业务操作:克隆对象与原型对象独立,修改互不影响
// (假设存在setLife方法)
pClone->setLife(50); // 仅修改克隆对象的生命值,原型对象不受影响
// 4. 释放资源
delete pClone; // 释放克隆对象
delete pProto; // 释放原型对象
return 0;
}
-
输出结果:
一个元素类怪物来到了这个世界 调用拷贝构造函数克隆元素类怪物 -
说明:克隆对象通过拷贝构造函数创建,与原型对象完全独立。
2.2.8关键总结
- 原型模式的核心是 “克隆现有对象”,而非 “从零创建对象”;
- 核心角色:抽象原型类(声明
clone接口)、具体原型类(实现clone接口); - 适用场景:对象内部数据复杂多变,且创建时需保留当前状态;
- 与工厂方法的核心区别:创建依据是 “现有对象” vs “类名”;
- 实现关键:正确处理拷贝构造函数(尤其是深拷贝),保证克隆对象独立。
第三节 建造者模式
3.1一个具体实现范例的逐步重构
3.1.1建造者模式基础认知
(1)模式定位与别名
- 建造者模式(Builder Pattern),又称构建器模式、生成器模式,属于创建型设计模式(与工厂模式、原型模式同属一类),核心用途是创建复杂对象。
- 适用场景:复杂对象的构建需按固定顺序分步骤进行(如房子建造:打地基→主体→装修;汽车组装:发动机→方向盘→轮胎;报表生成:表头→表身→表尾)。
(2)核心痛点与设计目标
- 痛点:复杂对象的构建步骤固定,但各部件的具体实现可能多变(如游戏中不同类型怪物的躯干、头部模型载入逻辑不同),直接耦合在对象类中会导致职责混乱、灵活性差。
- 目标:分离对象的构建流程与部件实现,让构建流程统一可控,部件实现可灵活替换,同时降低代码耦合度、提高复用性。
3.1.2案例背景:游戏怪物模型装配
(1)需求定义
- 怪物类型:亡灵类(M_Undead)、元素类(M_Element)、机械类(M_Mechanic)。
- 怪物组成:头部、躯干(含颈部、尾巴)、肢体(四肢),三部件需分步骤载入并挂接。
- 设计要求:同类型怪物的三部件可互换组合(如 3 个头部 + 3 个躯干 + 3 个肢体 = 27 种外观),节省制作成本和内存消耗。
- 装配步骤(固定流程):
- 载入躯干模型,提取位置 / 方向信息;
- 载入头部、肢体模型,提取位置 / 方向信息;
- 将头部、肢体按正确位置挂接到躯干,形成完整模型。
(2)关键注意点
- 不可在
Monster类的构造函数中调用虚函数(C++ 编程规范):因为构造子类对象时,父类构造函数先执行,此时虚函数未绑定子类实现,会导致逻辑错误。 - 模型编号规则:输入字符串(如 “1253679201254”)的不同子串对应不同部件编号(如 substr (4,3) 截取躯干编号),无需关注具体编码规则,只需按固定位置截取。
3.1.3逐步重构:从原始实现到建造者模式
阶段 1:原始实现(耦合式设计)
核心思路
将构建流程(装配步骤)和部件实现(模型载入)直接封装在Monster类及其子类中。
代码实现(关键部分)
namespace _nmsp1
{
// 怪物父类(产品父类)
class Monster
{
public:
virtual ~Monster() {} // 父类析构必须为虚函数
// 装配流程(固定步骤,类似模板方法)
void Assemble(string strmodelno)
{
LoadTrunkModel(strmodelno.substr(4, 3)); // 步骤1:载入躯干
LoadHeadModel(strmodelno.substr(7, 3)); // 步骤2:载入头部并挂接
LoadLimbsModel(strmodelno.substr(10, 3));// 步骤3:载入肢体并挂接
}
// 部件载入接口(纯虚函数,子类实现具体逻辑)
virtual void LoadTrunkModel(string strno) = 0;
virtual void LoadHeadModel(string strno) = 0;
virtual void LoadLimbsModel(string strno) = 0;
};
//亡灵类怪物
class M_Undead :public Monster
{
public:
virtual void LoadTrunkModel(string strno)
{
cout << "载入亡灵类怪物的躯干部位模型,需要调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
}
virtual void LoadHeadModel(string strno)
{
cout << "载入亡灵类怪物的头部模型并挂接到躯干部位,需要调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
}
virtual void LoadLimbsModel(string strno)
{
cout << "载入亡灵类怪物的四肢模型并挂接到躯干部位,需要调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
}
};
//元素类怪物
class M_Element :public Monster
{
public:
virtual void LoadTrunkModel(string strno)
{
cout << "载入元素类怪物的躯干部位模型,需要调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
}
virtual void LoadHeadModel(string strno)
{
cout << "载入元素类怪物的头部模型并挂接到躯干部位,需要调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
}
virtual void LoadLimbsModel(string strno)
{
cout << "载入元素类怪物的四肢模型并挂接到躯干部位,需要调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
}
};
//机械类怪物
class M_Mechanic :public Monster
{
public:
virtual void LoadTrunkModel(string strno)
{
cout << "载入机械类怪物的躯干部位模型,需要调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
}
virtual void LoadHeadModel(string strno)
{
cout << "载入机械类怪物的头部模型并挂接到躯干部位,需要调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
}
virtual void LoadLimbsModel(string strno)
{
cout << "载入机械类怪物的四肢模型并挂接到躯干部位,需要调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
}
};
}
// 主函数调用
int main()
{
_nmsp1::Monster* pmonster = new _nmsp1::M_Element(); //创建一只元素类怪物
pmonster->Assemble("1253679201254"); // 传入模型编号
delete pmonster;
return 0;
}
-
输出结果:
载入元素类怪物的躯干部位模型,需要调用M_Element类或其父类中其他诸多成员函数,逻辑代码略...... 载入元素类怪物的头部模型并挂接到躯干部位,需要调用M_Element类或其父类中其他诸多成员函数,逻辑代码略...... 载入元素类怪物的四肢模型并挂接到躯干部位,需要调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......
存在问题
- 职责耦合:
Monster类既要表示怪物本身(属性、行为),又要负责模型构建流程(Assemble)和部件载入接口,违反 “单一职责原则”。 - 复用性差:若其他生物(如 NPC)也需按 “躯干→头部→肢体” 流程构建,无法复用现有构建逻辑。
- 灵活性不足:构建流程修改需改动
Monster父类,不符合 “开闭原则”。
阶段 2:重构 1 - 分离构建逻辑到建造者类
核心思路
创建与Monster类同层次的建造者类,将构建流程(Assemble)和部件载入接口(LoadXXXModel)从Monster类中剥离,让Monster类仅负责表示怪物本身。
代码实现(关键部分)
namespace _nmsp1
{
// 怪物父类(产品父类)- 仅保留产品本身的职责
class Monster
{
public:
virtual ~Monster() {}
};
// 亡灵类/元素类/机械类怪物(具体产品)- 仅实现产品特有属性/行为
class M_Undead : public Monster {};
class M_Element : public Monster {};
class M_Mechanic : public Monster {};
// 怪物构建器父类(抽象建造者)- 封装构建逻辑和部件接口
class MonsterBuilder
{
public:
virtual ~MonsterBuilder() {}
// 装配流程(固定步骤,类似模板方法)
void Assemble(string strmodelno)
{
LoadTrunkModel(strmodelno.substr(4, 3)); // 步骤1:载入躯干
LoadHeadModel(strmodelno.substr(7, 3)); // 步骤2:载入头部并挂接
LoadLimbsModel(strmodelno.substr(10, 3));// 步骤3:载入肢体并挂接
}
// 返回构建完成的怪物对象
Monster* GetResult() { return m_pMonster; }
// 部件载入接口(纯虚函数,子类实现具体逻辑)
virtual void LoadTrunkModel(string strno) = 0;
virtual void LoadHeadModel(string strno) = 0;
virtual void LoadLimbsModel(string strno) = 0;
protected:
Monster* m_pMonster; // 关联要构建的怪物对象
};
//亡灵类怪物构建器类
class M_UndeadBuilder :public MonsterBuilder
{
public:
M_UndeadBuilder() //构造函数
{
m_pMonster = new M_Undead();
}
virtual void LoadTrunkModel(string strno)
{
cout << "载入亡灵类怪物的躯干部位模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//具体要做的事情其实是委托给怪物子类完成,委托指把本该自己实现的功能转给其他类实现
//m_pMonster->......略
}
virtual void LoadHeadModel(string strno)
{
cout << "载入亡灵类怪物的头部模型并挂接到躯干部位,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadLimbsModel(string strno)
{
cout << "载入亡灵类怪物的四肢模型并挂接到躯干部位,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
};
//元素类怪物构建器类
class M_ElementBuilder :public MonsterBuilder
{
public:
M_ElementBuilder() //构造函数
{
m_pMonster = new M_Element();
}
virtual void LoadTrunkModel(string strno)
{
cout << "载入元素类怪物的躯干部位模型,需要m_pMonster指针调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadHeadModel(string strno)
{
cout << "载入元素类怪物的头部模型并挂接到躯干部位,需要m_pMonster指针调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadLimbsModel(string strno)
{
cout << "载入元素类怪物的四肢模型并挂接到躯干部位,需要m_pMonster指针调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
};
//机械类怪物构建器类
class M_MechanicBuilder :public MonsterBuilder
{
public:
M_MechanicBuilder() //构造函数
{
m_pMonster = new M_Mechanic();
}
virtual void LoadTrunkModel(string strno)
{
cout << "载入机械类怪物的躯干部位模型,需要m_pMonster指针调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadHeadModel(string strno)
{
cout << "载入机械类怪物的头部模型并挂接到躯干部位,需要m_pMonster指针调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadLimbsModel(string strno)
{
cout << "载入机械类怪物的四肢模型并挂接到躯干部位,需要m_pMonster指针调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
};
}
核心改进
- 职责分离:
Monster类仅作为产品,MonsterBuilder类负责构建逻辑,符合单一职责原则。 - 复用性提升:其他生物(如 NPC)可复用
MonsterBuilder的构建流程,只需创建对应的建造者子类。 - 解耦:产品与构建逻辑分离,修改构建逻辑无需改动产品类。
阶段 3:重构 2 - 引入指挥者类(标准建造者模式)
核心思路
将固定的构建流程(Assemble)从MonsterBuilder中剥离,创建指挥者类(Director) ,由指挥者控制构建步骤,建造者仅负责部件实现。
代码实现(关键部分)
namespace _nmsp1
{
//怪物构建器父类
class MonsterBuilder
{
public:
virtual ~MonsterBuilder() {} //做父类时析构函数应该为虚函数
//返回指向Monster类的成员变量指针m_pMonster,当一个复杂的对象构建完成后,可以通过该成员函数把对象返回。
Monster* GetResult()
{
return m_pMonster;
}
virtual void LoadTrunkModel(string strno) = 0; //这里也可以写成一个空函数,子类决定是否重新实现
virtual void LoadHeadModel(string strno) = 0;
virtual void LoadLimbsModel(string strno) = 0;
protected:
Monster* m_pMonster; //指向Monster类的成员变量指针
};
//----------
//亡灵类怪物构建器类
class M_UndeadBuilder :public MonsterBuilder
{
public:
M_UndeadBuilder() //构造函数
{
m_pMonster = new M_Undead();
}
virtual void LoadTrunkModel(string strno)
{
cout << "载入亡灵类怪物的躯干部位模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//具体要做的事情其实是委托给怪物子类完成,委托指把本该自己实现的功能转给其他类实现
//m_pMonster->......略
}
virtual void LoadHeadModel(string strno)
{
cout << "载入亡灵类怪物的头部模型并挂接到躯干部位,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadLimbsModel(string strno)
{
cout << "载入亡灵类怪物的四肢模型并挂接到躯干部位,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
};
//元素类怪物构建器类
class M_ElementBuilder :public MonsterBuilder
{
public:
M_ElementBuilder() //构造函数
{
m_pMonster = new M_Element();
}
virtual void LoadTrunkModel(string strno)
{
cout << "载入元素类怪物的躯干部位模型,需要m_pMonster指针调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadHeadModel(string strno)
{
cout << "载入元素类怪物的头部模型并挂接到躯干部位,需要m_pMonster指针调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadLimbsModel(string strno)
{
cout << "载入元素类怪物的四肢模型并挂接到躯干部位,需要m_pMonster指针调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
};
//机械类怪物构建器类
class M_MechanicBuilder :public MonsterBuilder
{
public:
M_MechanicBuilder() //构造函数
{
m_pMonster = new M_Mechanic();
}
virtual void LoadTrunkModel(string strno)
{
cout << "载入机械类怪物的躯干部位模型,需要m_pMonster指针调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadHeadModel(string strno)
{
cout << "载入机械类怪物的头部模型并挂接到躯干部位,需要m_pMonster指针调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
virtual void LoadLimbsModel(string strno)
{
cout << "载入机械类怪物的四肢模型并挂接到躯干部位,需要m_pMonster指针调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl;
//m_pMonster->......略
}
};
// 指挥者类 - 控制构建流程,不依赖具体产品
class MonsterDirector
{
public:
// 构造时传入具体建造者
MonsterDirector(MonsterBuilder* ptmpBuilder) { m_pMonsterBuilder = ptmpBuilder; }
// 动态切换建造者
void SetBuilder(MonsterBuilder* ptmpBuilder) { m_pMonsterBuilder = ptmpBuilder; }
// 构建流程(固定步骤,原Assemble方法)
Monster* Construct(string strmodelno)
{
m_pMonsterBuilder->LoadTrunkModel(strmodelno.substr(4, 3));
m_pMonsterBuilder->LoadHeadModel(strmodelno.substr(7, 3));
m_pMonsterBuilder->LoadLimbsModel(strmodelno.substr(10, 3));
return m_pMonsterBuilder->GetResult(); // 返回构建完成的产品
}
private:
MonsterBuilder* m_pMonsterBuilder; // 持有抽象建造者指针(依赖倒置)
};
}
// 主函数调用(标准建造者模式使用流程)
int main()
{
// 1. 创建具体建造者(指定产品类型)
_nmsp1::MonsterBuilder* pMonsterBuilder = new _nmsp1::M_UndeadBuilder(); //创建亡灵类怪物构建器对象
// 2. 创建指挥者,传入建造者
_nmsp1::MonsterDirector* pDirector = new _nmsp1::MonsterDirector(pBuilder);
// 3. 指挥者控制构建流程,返回产品
_nmsp1::Monster* pMonster = pDirector->Construct("1253679201254");//这里就构建出了一个完整的怪物对象
// 4. 释放资源
delete pMonster;
delete pDirector;
delete pBuilder;
return 0;
}
核心改进
- 流程封装:用户无需关心构建步骤(如先载入躯干还是头部),只需通过指挥者调用
Construct即可。 - 灵活性极致:切换产品类型只需更换具体建造者(如
new M_ElementBuilder()),修改流程只需改动指挥者的Construct方法。 - 依赖倒置:指挥者依赖抽象建造者(
MonsterBuilder),不依赖具体建造者或产品,符合 “开闭原则”。
3.1.4建造者模式核心角色(结合代码对应)
| 角色 | 职责描述 | 代码对应类 |
|---|---|---|
| 产品(Product) | 被构建的复杂对象,由多个部件组成。 | Monster、M_Undead、M_Element等 |
| 抽象建造者(Abstract Builder) | 定义构建部件的接口(LoadXXXModel)和返回产品的方法(GetResult)。 |
MonsterBuilder |
| 具体建造者(Concrete Builder) | 实现抽象建造者接口,构建具体产品的部件,持有产品实例。 | M_UndeadBuilder、M_ElementBuilder等 |
| 指挥者(Director) | 调用具体建造者的接口,控制构建流程(固定步骤),不依赖具体产品或建造者。 | MonsterDirector |

3.1.5补充知识点:原型模式(老师笔记扩展)
(1) 原型模式定义
用原型实例指定创建对象的种类,通过拷贝(克隆) 这些原型创建新对象,无需手动初始化复杂状态。
(2) 核心角色(结合本案例)
| 角色 | 职责描述 | 代码对应类 |
|---|---|---|
| 抽象原型类(Prototype) | 定义克隆自身的接口(通常是clone方法)。 |
Monster(需添加clone方法) |
| 具体原型类(Concrete Prototype) | 实现克隆接口,返回自身的拷贝。 | M_Undead、M_Element等(实现clone) |
(3)与工厂方法模式的异同
| 对比维度 | 工厂方法模式 | 原型模式 |
|---|---|---|
| 相同点 | 无需程序员知道所创建对象的类名 | 无需程序员知道所创建对象的类名 |
| 不同点(创建方式) | 根据类名动态创建新对象(new) |
根据现有对象克隆创建新对象(clone) |
| 适用场景 | 产品类型固定,初始化逻辑简单 | 产品内部数据复杂多变,需复用当前状态 |
(4)原型模式优缺点
- 优点:
- 创建复杂对象效率高(克隆避免重复初始化);
- 无额外工厂类等级结构,代码简洁;
- 可灵活复用对象状态(如克隆一个已装配好的怪物模型)。
- 缺点:
- 需实现克隆接口,涉及深拷贝 / 浅拷贝问题(需谨慎处理指针成员);
- 若对象层级较深,克隆逻辑可能复杂。
(5)核心结论
原型模式可看作 “特殊的工厂方法模式”,核心是 “克隆现有对象” 而非 “创建新对象”,适用于复杂对象的快速复用场景。
3.1.6关键总结
- 建造者模式的核心是分离构建流程与部件实现,适用于 “步骤固定、部件多变” 的复杂对象创建;
- 标准建造者模式必须包含 4 个角色,其中指挥者封装流程,建造者实现部件,产品仅负责自身职责;
- 原始实现→建造者类→指挥者类的重构过程,体现了 “职责单一”“依赖倒置” 等设计原则;
- 原型模式与建造者模式无直接冲突,可结合使用(如用原型模式克隆怪物部件,再用建造者模式装配)。
3.1.7代码执行流程说明(主函数示例)
- 创建具体建造者
M_UndeadBuilder,其构造函数内部创建M_Undead对象(产品); - 创建指挥者
MonsterDirector,传入建造者对象,指挥者持有建造者指针; - 调用指挥者
Construct方法,指挥者按固定步骤调用建造者的LoadXXXModel方法,装配部件; - 建造者通过
GetResult返回装配完成的M_Undead对象; - 释放产品、指挥者、建造者资源,程序结束。
执行结果:
载入亡灵类怪物的躯干部位模型,通过m_pMonster调用子类方法...略
载入亡灵类怪物的头部模型并挂接...略
载入亡灵类怪物的四肢模型并挂接...略
3.2引入建造者模式
3.2.1建造者模式核心定义与核心思想
(1)模式定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 核心分离:
构建过程(如何组装部件)与对象表示(最终成品的具体形态)分离。 - 核心价值:复用相同的构建流程,生成不同类型、不同格式的复杂对象。
(2)核心思想对应第一个范例(怪物构建)
- 构建:指
MonsterBuilder类族(负责部件创建与装配)和MonsterDirector类(负责构建流程控制)。 - 表示:指
Monster类族(最终创建的亡灵类、元素类、机械类怪物对象)。 - 构建过程稳定:
MonsterDirector::Construct中的部件装配顺序(躯干→头部→四肢)固定不变。 - 表示可变化:传入不同的
ConcreteBuilder(如M_UndeadBuilder),即可生成不同类型的怪物。
3.2.2建造者模式的四大角色(结合怪物范例代码解析)
建造者模式包含四个核心角色,每个角色在代码中都有明确对应的类,职责划分清晰:
(1)抽象构建器(Builder)
-
定义:为创建复杂对象的各个部件指定抽象接口,同时提供返回最终产品的接口。
-
代码对应:
_nmsp1::MonsterBuilder类class MonsterBuilder { public: virtual ~MonsterBuilder() {} // 部件构建接口(抽象方法,由子类实现具体部件创建) virtual void LoadTrunkModel(string strno) = 0; // 构建躯干部件 virtual void LoadHeadModel(string strno) = 0; // 构建头部部件 virtual void LoadLimbsModel(string strno) = 0; // 构建四肢部件 // 返回最终产品的接口 Monster* GetResult() { return m_pMonster; } protected: Monster* m_pMonster; // 持有产品对象指针,子类初始化具体产品 }; -
核心职责:定义 “做什么部件”,不关心 “怎么做”(具体实现交给子类),并提供产品获取入口。
(2)具体构建器(ConcreteBuilder)
-
定义:实现抽象构建器的接口,负责具体部件的构造与装配,明确自身创建的产品类型。
-
代码对应:
M_UndeadBuilder、M_ElementBuilder、M_MechanicBuilder类//亡灵类怪物构建器类 class M_UndeadBuilder :public MonsterBuilder { public: M_UndeadBuilder() //构造函数 { m_pMonster = new M_Undead(); } virtual void LoadTrunkModel(string strno) { cout << "载入亡灵类怪物的躯干部位模型,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl; //具体要做的事情其实是委托给怪物子类完成,委托指把本该自己实现的功能转给其他类实现 //m_pMonster->......略 } virtual void LoadHeadModel(string strno) { cout << "载入亡灵类怪物的头部模型并挂接到躯干部位,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl; //m_pMonster->......略 } virtual void LoadLimbsModel(string strno) { cout << "载入亡灵类怪物的四肢模型并挂接到躯干部位,需要m_pMonster指针调用M_Undead类或其父类中其他诸多成员函数,逻辑代码略......" << endl; //m_pMonster->......略 } }; //元素类怪物构建器类 class M_ElementBuilder :public MonsterBuilder { public: M_ElementBuilder() //构造函数 { m_pMonster = new M_Element(); } virtual void LoadTrunkModel(string strno) { cout << "载入元素类怪物的躯干部位模型,需要m_pMonster指针调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl; //m_pMonster->......略 } virtual void LoadHeadModel(string strno) { cout << "载入元素类怪物的头部模型并挂接到躯干部位,需要m_pMonster指针调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl; //m_pMonster->......略 } virtual void LoadLimbsModel(string strno) { cout << "载入元素类怪物的四肢模型并挂接到躯干部位,需要m_pMonster指针调用M_Element类或其父类中其他诸多成员函数,逻辑代码略......" << endl; //m_pMonster->......略 } }; //机械类怪物构建器类 class M_MechanicBuilder :public MonsterBuilder { public: M_MechanicBuilder() //构造函数 { m_pMonster = new M_Mechanic(); } virtual void LoadTrunkModel(string strno) { cout << "载入机械类怪物的躯干部位模型,需要m_pMonster指针调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl; //m_pMonster->......略 } virtual void LoadHeadModel(string strno) { cout << "载入机械类怪物的头部模型并挂接到躯干部位,需要m_pMonster指针调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl; //m_pMonster->......略 } virtual void LoadLimbsModel(string strno) { cout << "载入机械类怪物的四肢模型并挂接到躯干部位,需要m_pMonster指针调用M_Mechanic类或其父类中其他诸多成员函数,逻辑代码略......" << endl; //m_pMonster->......略 } }; -
核心职责:实现 “怎么做部件”,绑定具体产品,完成部件的实际创建。
(3)产品(Product)
-
定义:被构建的复杂对象,包含多个部件,由具体构建器创建其内部表示并完成装配。
-
代码对应:
M_Undead、M_Element、M_Mechanic类(继承自Monster父类)//怪物父类 class Monster { public: virtual ~Monster() {} //做父类时析构函数应该为虚函数 }; //亡灵类怪物 class M_Undead :public Monster { }; //元素类怪物 class M_Element :public Monster { }; //机械类怪物 class M_Mechanic :public Monster { }; -
核心职责:封装复杂对象的属性和行为,是构建过程的最终产出物。
(4)指挥者(Director)
-
定义:又称导演类,持有抽象构建器指针,控制复杂对象的构建流程(步骤顺序),不直接参与部件创建。
-
代码对应:
_nmsp1::MonsterDirector类class MonsterDirector { public: MonsterDirector(MonsterBuilder* ptmpBuilder) { m_pMonsterBuilder = ptmpBuilder; // 绑定抽象构建器(依赖倒置) } // 切换构建器(支持动态更换产品类型) void SetBuilder(MonsterBuilder* ptmpBuilder) { m_pMonsterBuilder = ptmpBuilder; } // 构建流程控制(核心方法) Monster* Construct(string strmodelno) { // 固定构建顺序:躯干 → 头部 → 四肢(构建过程稳定) m_pMonsterBuilder->LoadTrunkModel(strmodelno.substr(4, 3)); m_pMonsterBuilder->LoadHeadModel(strmodelno.substr(7, 3)); m_pMonsterBuilder->LoadLimbsModel(strmodelno.substr(10, 3)); return m_pMonsterBuilder->GetResult(); // 返回成品 } private: MonsterBuilder* m_pMonsterBuilder; // 依赖抽象,不依赖具体 }; -
核心职责:定义 “按什么顺序做部件”,隔离客户端与构建过程,通过切换构建器实现 “同流程不同产品”。
(5)角色关系总结(UML 核心关联)
- 指挥者(
MonsterDirector)持有抽象构建器(MonsterBuilder)的指针(空心菱形表示 “包含” 关系)。 - 具体构建器继承自抽象构建器,产品子类继承自抽象产品。
- 客户端仅需与指挥者和具体构建器交互,无需关心部件创建细节。

3.2.3建造者模式的关键原则
(1)拆分与合并原则
- 复杂对象:建议拆分指挥者(
Director)与构建器(Builder),如怪物范例中,将构建流程(Construct)从MonsterBuilder拆分到MonsterDirector,降低单个类的复杂度。 - 简单对象:可省略指挥者,将构建流程合并到抽象构建器中(退化模式),减少类数量。
(2)客户端使用流程(怪物范例客户端代码)
// 1. 创建具体构建器(指定产品类型:亡灵类)
_nmsp1::MonsterBuilder* pBuilder = new _nmsp1::M_UndeadBuilder();
// 2. 创建指挥者并绑定构建器
_nmsp1::MonsterDirector* pDirector = new _nmsp1::MonsterDirector(pBuilder);
// 3. 指挥者控制构建流程,生成产品
_nmsp1::Monster* pMonster = pDirector->Construct("1253679201254");
// 4. 释放资源
delete pMonster;
delete pDirector;
delete pBuilder;
- 客户端仅需关注 “选什么构建器”,无需关心 “部件怎么造”“流程怎么排”。
3.2.4第二个范例:工作日报导出(建造者模式的实际应用)
(1)需求分析
- 核心对象:员工工作日报(包含标题、内容主体、结尾 三部分)。
- 标题:部门名称、日报生成日期。
- 内容主体:多条工作记录(工作描述 + 花费时间)。
- 结尾:员工姓名。
- 核心需求:将日报导出为多种格式(纯文本、XML、JSON),且导出步骤固定(拼接标题→拼接主体→拼接结尾)。
(2)不用设计模式的实现(代码解析)
直接为每种导出格式编写独立类,重复实现导出步骤,复用性差。
①数据模型类(封装日报的三个部分)
namespace _nmsp2
{
//日报的“标题”部分
class DailyHeaderData
{
public:
//构造函数
DailyHeaderData(string strDepName, string strGenData) :m_strDepName(strDepName), m_strGenData(strGenData) {}
string getDepName() //获取部门名称
{
return m_strDepName;
}
string getExportDate() //获取日报生成日期
{
return m_strGenData;
}
private:
string m_strDepName;//部门名称
string m_strGenData; //日报生成日期
};
//日报中的“内容主体”部分 中的 每一条描述数据
class DailyContentData
{
public:
//构造函数
DailyContentData(string strContent, double dspendTime) :m_strContent(strContent), m_dspendTime(dspendTime) {}
string getContent() //获取该项工作内容描述
{
return m_strContent;
}
double getSpendTime() //获取完成该项工作花费的时间
{
return m_dspendTime;
}
private:
string m_strContent; //该项工作内容描述
double m_dspendTime; //完成该项工作花费的时间(单位:小时)
};
//日报中的“结尾”部分
class DailyFooterData
{
public:
//构造函数
DailyFooterData(string strUserName) :m_strUserName(strUserName) {}
string getUserName() //获取日报所属员工姓名
{
return m_strUserName;
}
private:
string m_strUserName; //日报所属员工姓名
};
}
②导出类(纯文本 + XML 格式)
namespace _nmsp2
{
//将日报导出到纯文本格式文件 相关的类
class ExportToTxtFile
{
public:
//实现导出动作
void doExport(DailyHeaderData& dailyheaderobj, vector<DailyContentData*>& vec_dailycontobj, DailyFooterData& dailyfooterobj)
{
string strtmp = "";
//(1)拼接标题
strtmp += dailyheaderobj.getDepName() + "," + dailyheaderobj.getExportDate() + "\n";
//(2)拼接内容主体,内容主体中的描述数据会有多条,因此,需要迭代
for (auto iter = vec_dailycontobj.begin(); iter != vec_dailycontobj.end(); ++iter)
{
ostringstream oss; //#include <sstream>
oss << (*iter)->getSpendTime();
strtmp += (*iter)->getContent() + ":(花费的时间:" + oss.str() + "小时)" + "\n";
} //end for
//(3)拼接结尾
strtmp += "报告人:" + dailyfooterobj.getUserName() + "\n";
//(4)导出到真实文件的代码略,只展示在屏幕上文件的内容
cout << strtmp;
}
};
//将日报导出到XML格式文件 相关的类
class ExportToXmlFile
{
public:
//实现导出动作
void doExport(DailyHeaderData& dailyheaderobj, vector<DailyContentData*>& vec_dailycontobj, DailyFooterData& dailyfooterobj)
{
string strtmp = "";
//(1)拼接标题
strtmp += "<?xml version =\"1.0\" encoding=\"UTF-8\" ?>\n";
strtmp += "<DailyReport>\n";
strtmp += " <Header>\n";
strtmp += " <DepName>" + dailyheaderobj.getDepName() + "</DepName>\n";
strtmp += " <GenDate>" + dailyheaderobj.getExportDate() + "</GenDate>\n";
strtmp += " </Header>\n";
//(2)拼接内容主体 ,内容主体中的描述数据会有多条,因此需要迭代
strtmp += " <Body>\n";
for (auto iter = vec_dailycontobj.begin(); iter != vec_dailycontobj.end(); ++iter)
{
ostringstream oss;
oss << (*iter)->getSpendTime();
strtmp += " <Content>" + (*iter)->getContent() + "</Content>\n";
strtmp += " <SpendTime>花费的时间:" + oss.str() + "小时" + "</SpendTime>\n";
} //end for
strtmp += " </Body>\n";
//(3)拼接结尾
strtmp += " <Footer>\n";
strtmp += " <UserName>报告人:" + dailyfooterobj.getUserName() + "</UserName>\n";
strtmp += " </Footer>\n";
strtmp += "</DailyReport>\n";
//(4)导出到真实文件的代码略,只展示在屏幕上文件的内容
cout << strtmp;
}
};
}
③客户端调用
// 1. 构造日报数据
_nmsp2::DailyHeaderData* pdhd = new _nmsp2::DailyHeaderData("研发一部", "11月1日");
_nmsp2::DailyContentData* pdcd1 = new _nmsp2::DailyContentData("完成A项目需求分析", 3.5);
_nmsp2::DailyContentData* pdcd2 = new _nmsp2::DailyContentData("确定A项目开发工具", 4.5);
vector<_nmsp2::DailyContentData*> vec_dcd;
vec_dcd.push_back(pdcd1);
vec_dcd.push_back(pdcd2);
_nmsp2::DailyFooterData* pdfd = new _nmsp2::DailyFooterData("小李");
// 2. 导出为XML格式(切换为ExportToTxtFile即可导出纯文本)
_nmsp2::ExportToXmlFile file_etxml;
file_etxml.doExport(*pdhd, vec_dcd, *pdfd);
// 3. 释放资源
delete pdhd;
for (auto iter = vec_dcd.begin(); iter != vec_dcd.end(); ++iter)
{
delete (*iter);
}
delete pdfd;
④存在的问题
- 导出步骤(拼接标题→主体→结尾)在每个导出类中重复编写,冗余严重。
- 新增格式(如 JSON)时,需重新编写完整步骤,违反 “开闭原则”。
- 构建流程(步骤顺序)与格式实现(细节)耦合,修改流程需改动所有导出类。
(3)建造者模式的应用初衷
将构建不同格式数据的细节实现代码,与具体的构建步骤分离,达到复用构建步骤的目的。
- 复用步骤:将 “拼接标题→主体→结尾” 抽象为通用构建流程(指挥者负责)。
- 分离细节:每种格式的拼接逻辑(如 XML 标签、TXT 逗号分隔)交给具体构建器。
- 扩展方便:新增 JSON 格式时,仅需新增具体构建器,无需修改流程代码。
3.2.5建造者模式总结
(1)适用场景
- 创建复杂对象(包含多个部件),且构建流程稳定(步骤顺序固定)。
- 需生成同一复杂对象的不同表示(如不同类型的怪物、不同格式的日报)。
- 希望隔离复杂对象的构建过程与客户端,客户端无需关心部件创建和装配细节。
(2)核心优势
- 解耦构建与表示:相同流程生成不同产品,复用性强。
- 控制构建顺序:指挥者统一管理流程,避免步骤混乱。
- 扩展性好:新增产品仅需新增具体构建器,符合 “开闭原则”。
- 封装性好:客户端无需了解部件创建细节,降低使用复杂度。
(3)注意事项
- 若对象简单(无多个部件 / 无固定构建流程),无需使用建造者模式(过度设计)。
- 抽象构建器的接口需设计合理,避免新增部件时频繁修改接口(可结合接口隔离原则)。
- 注意内存释放:产品对象通常由具体构建器创建,需明确释放责任(如客户端释放或构建器内部管理)。
参考资料来源:王健伟

浙公网安备 33010602011771号