C++11 设计模式5. 原型模式

什么是原型模式?

原型模式⼀种创建型设计模式,该模式的核⼼思想是基于现有的对象创建新的对象 ,⽽不是从头开始创建。在原型模式中,通常有⼀个原型对象,它被⽤作创建新对象的模板。新对象通过复制原型对象的属性和状态来创建,⽽⽆需知道具体的创建细节。

Prototype模式说简单点,就是提供了一个clone, 通过已存在对象进行新对象创建。clone()实现和具体的实现语言相关,在C++中我们通过拷贝构造函数实现。

那为啥要写clone的接口来实现这个目的呢?直接使用拷贝构造不香么,知乎中看到陈硕大佬对此的一个回答,觉得豁然开朗。

Prototype 的意义在于,你拿到一个 基类指针 Base* ,
它指向某个 派生类 Derived 对象,
你想克隆出 Derived对象,但代码中不写出 Derived 的具体类型,
因为有很多派生类,这种情况下你用构造函数是搞不定的。
switch-case 是 bad smells 。
另外,这里考虑 virtual 的性能损失是主次不分,
构造对象需要分配内存,这开销比一次虚函数调用大多了。
--陈硕在知乎中的回答

复习拷贝构造函数实现

在学习原型模式之前,我们先来看一下C++中深拷贝和先拷贝问题

UML

两种角色:

  1. prototype(抽象原型类) Monster类

  2. ConcretePrototype 具体原型类:在clone方法中 return一个自己的对象

M_undead,M_Element,M_Mechanic

那么什么时候才能使用这个原型对象呢?

我们以 魔兽争霸 这款游戏中的 分身系英雄为例,当 剑圣 出镜像的时候,5狗齐飞,分身斧,这些道具或者技能使用的时候,分裂出来的这些英雄都应该有当前英雄的这些属性,然后将属性微调就可以,例如,分身的攻击力只有原先的30%,受到伤害是原先的150%。就可以用 原型模型。

这里的核心是 魔兽争霸的英雄 属性太多了,还有6个框框的物品栏,分裂的瞬间,都要将这些属性拷贝过去。--也就是说:

如果对象的内部数据比较复杂多变,并且在创建对象的时候希望爆出对象的当前状态,那么用原型模式显然比用工厂方法模式更合适

工厂方法模式和原型模式在创建对象时的异同点

a。都不需要程序员知道所创建对象所属的类名

b.工厂方法模式中的createMonster仍旧属于根据类名来生成新对象

c 原型模式clone是根据现有对象来生成新对象,会调用 copy构造方法 。

d 原型模型不像工厂模式,不需要额外的等级结构创建多个工厂类

原型模式的优缺点:

如果创建新对象的背部数据比较复杂且多变,原型模式创建对象的效率可能高的多

需要在clone中完成对象的拷贝,特别是深拷贝和浅拷贝问题,但是一般都调用 copy构造方法

当前copy构造函数中也可以调用 operator= 的重写。

这样也可以使用 "= " 完成拷贝

原型模式和直接 M_undead(m_undead1) 有啥区别?

既然原型模式的 也是调用 copy构造函数,那么直接用 如下的代码不就行了吗?为啥还弄个原型模式?

M_undead m_undead2 = M_undead(m_undead1)

  1. C++才有 copy构造,java ,C#并没有,设计模式是独立于变成 语言存在的,因此原型模式的存在是有意义的。

2.你拿到一个 基类指针 Base* ,

它指向某个 派生类 Derived 对象,

你想克隆出 Derived对象,但代码中不写出 Derived 的具体类型,

因为有很多派生类,这种情况下你用构造函数是搞不定的。

switch-case 是 bad smells 。

代码

在代码中还复习了 移动构造函数,和 移动operator=运算符的重写。

// 原型模型

//原型模式⼀种创建型设计模式,
//该模式的核⼼思想是基于现有的对象创建新的对象,
//⽽不是从头开始创建。在原型模式中,通常有⼀个原型对象,
//它被⽤作创建新对象的模板。
//新对象通过复制原型对象的属性和状态来创建,⽽⽆需知道具体的创建细节。
//
//Prototype模式说简单点,就是提供了一个clone, 
//通过已存在对象进行新对象创建。
//clone()实现和具体的实现语言相关,
//在C++中我们通过拷贝构造函数实现。
//
//那为啥要写clone的接口来实现这个目的呢?
//直接使用拷贝构造不香么?
//
//Prototype 的意义在于,你拿到一个 基类指针 Base* ,
//它指向某个 派生类 Derived 对象,
//你想克隆出 Derived对象,但代码中不写出 Derived 的具体类型,
//因为有很多派生类,这种情况下你用构造函数是搞不定的。
//switch - case 是 bad smells 。
//另外,这里考虑 virtual 的性能损失是主次不分,
//构造对象需要分配内存,这开销比一次虚函数调用大多了。

//
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "stdlib.h"
#include "stdio.h"
#include "stdint.h"
using namespace std;



//在学习原型模式之前,需要先复习一下 C++ 的 深拷贝浅拷贝问题。
//涉及到的知识点:拷贝构造函数,重写赋值运算符函数,移动构造函数,重写移动赋值运算符函数

class Teacher
{
public:

	Teacher() = default;//=default  表示C++编译器给无参数的构造方法生成函数体

	//有参数的构造方法
	Teacher(int age,string sname,char* name,char *othername,char **stuname)
		:_age(age),_sname(sname), _othername(othername),_stuname(stuname){
		cout << "Teacher 有参数的构造方法被调用" << endl;
		strcpy(_name, name);
	}

	//拷贝构造函数 正常的写法
	//Teacher(const Teacher& obj) {
	//	this->_age = obj._age;
	//	strcpy(_name, obj._name);
	//	this->_sname = obj._sname;
	//	
	//	//如果原先的othername不为nullptr,则先要将原先的othername delete掉。实际代码中
	//	if (this->_othername!= nullptr) {
	//		delete this->_othername;
	//		this->_othername = nullptr;
	//	}
	//	if (this->_stuname != nullptr) {
	//		for (int i = 0; i < 5; i++)
	//		{
	//			delete this->_stuname[i];
	//			this->_stuname[i] = nullptr;
	//		}
	//		delete this->_stuname;
	//		this->_stuname = nullptr;
	//		//delete [] this->_stuname;
	//	}
	//	if (obj._othername != nullptr) {
	//		int othernamelen = strlen(obj._othername) + 1;
	//		this->_othername = (char *)malloc(sizeof(char) * othernamelen);
	//		strcpy(this->_othername,obj._othername);
	//	}
	//	if (obj._stuname != nullptr) {
	//		this->_stuname = (char **)malloc(sizeof(char *) * 5);
	//		for (int i = 0; i < 5; i++)
	//		{
	//			if (obj._stuname[i] != nullptr) {
	//				int stunamelen = strlen(obj._stuname[i]) + 1;
	//				this->_stuname[i] = (char *)malloc(sizeof(char *) *stunamelen);
	//				strcpy(this->_stuname[i],obj._stuname[i]);
	//			}
	//		}
	//	}

	//}

	//拷贝构造函数
	Teacher(const Teacher& obj) {
		cout << "Teacher 拷贝构造函数被调用" << endl;
		*this = obj;
	}
	//operator = 函数 重写
	Teacher& operator=( const Teacher & obj) {
		cout << "Teacher 赋值运算符函数被调用" << endl;
		this->_age = obj._age;
		strcpy(_name, obj._name);
		this->_sname = obj._sname;

		//如果原先的othername不为nullptr,则先要将原先的othername delete掉。实际代码中
		if (this->_othername != nullptr) {
			free( this->_othername);
			this->_othername = nullptr;
		}
		if (this->_stuname != nullptr) {
			for (int i = 0; i < 5; i++)
			{
				free( this->_stuname[i]);
				this->_stuname[i] = nullptr;
			}
			free( this->_stuname);
			this->_stuname = nullptr;
			//delete [] this->_stuname;
		}
		if (obj._othername != nullptr) {
			int othernamelen = strlen(obj._othername) + 1;
			this->_othername = (char *)malloc(sizeof(char) * othernamelen);
			strcpy(this->_othername, obj._othername);
		}
		if (obj._stuname != nullptr) {
			this->_stuname = (char **)malloc(sizeof(char *) * 5);
			for (int i = 0; i < 5; i++)
			{
				if (obj._stuname[i] != nullptr) {
					int stunamelen = strlen(obj._stuname[i]) + 1;
					this->_stuname[i] = (char *)malloc(sizeof(char *) *stunamelen);
					strcpy(this->_stuname[i], obj._stuname[i]);
				}
			}
		}
		return *this;
	}


	//移动拷贝函数
	Teacher( Teacher &&obj) {
		cout << "Teacher 移动构造函数被调用" << endl;
		*this = move(obj);
	}

	//移动赋值运算符 重写
	Teacher& operator=(Teacher && obj) {
		cout << "Teacher 移动赋值运算符 被调用" << endl;
		this->_age = obj._age;
		strcpy(_name, obj._name);
		this->_sname = obj._sname;

		//如果原先的othername不为nullptr,则先要将原先的othername delete掉。实际代码中
		if (this->_othername != nullptr) {
			free( this->_othername);
			this->_othername = nullptr;
		}
		if (this->_stuname != nullptr) {
			for (int i = 0; i < 5; i++)
			{
				free( this->_stuname[i]);
				this->_stuname[i] = nullptr;
			}
			free( this->_stuname);
			this->_stuname = nullptr;
			//delete [] this->_stuname;
		}
		if (obj._othername != nullptr) {
			int othernamelen = strlen(obj._othername) + 1;
			this->_othername = (char *)malloc(sizeof(char) * othernamelen);
			strcpy(this->_othername, obj._othername);
		}
		if (obj._stuname != nullptr) {
			this->_stuname = (char **)malloc(sizeof(char *) * 5);
			for (int i = 0; i < 5; i++)
			{
				if (obj._stuname[i] != nullptr) {
					int stunamelen = strlen(obj._stuname[i]) + 1;
					this->_stuname[i] = (char *)malloc(sizeof(char *) *stunamelen);
					strcpy(this->_stuname[i], obj._stuname[i]);
				}
			}
		}

		//
		if (obj._othername != nullptr) {
			free(obj._othername);
			obj._othername = nullptr;
		}
		if (obj._stuname!=nullptr) {
			for (int i = 0; i < 5; i++)
			{
				if (obj._stuname[i] !=nullptr) {
					free( obj._stuname[i]);
					obj._stuname[i] = nullptr;
				}
			}
			free( obj._stuname);
			obj._stuname = nullptr;
		}

		return *this;
	}

	// 写一个clone函数,内部调用 拷贝构造方法,
	Teacher* clone() {
		return new Teacher(*this);
	}
	~Teacher() {
		if (this->_othername != nullptr) {
			free(this->_othername);
			this->_othername = nullptr;
		}
		if (this->_stuname != nullptr) {
			for (int i = 0; i < 5; i++)
			{
				free(this->_stuname[i]);
				this->_stuname[i] = nullptr;
			}
			free(this->_stuname);
			this->_stuname = nullptr;
			//delete [] this->_stuname;
		}
	}
public:
	int _age = 0 ;
	string _sname = "";
	char _name[128] = {0};
	char *_othername = nullptr;
	char **_stuname = nullptr;
};
void printTea(Teacher *tea) {
	if (tea == NULL) {
		return;
	}
	printf("------ printTea start ------\n");
	printf("tea->age = %d,tea->name = %s,tea->othername=%s\n",
		tea->_age,
		tea->_name,
		tea->_othername);
	for (size_t j = 0; j < 5; j++)
	{
		printf("tea->stuname[%d] = %s,  ",
			j, tea->_stuname[j]);
	}
	printf("\n");
	printf("------ printTea end ------\n");


}
int main()
{

	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到"输出"窗口

    std::cout << "Hello World!\n";
	Teacher tea;
	tea._age = 10;
	strcpy(tea._name, "tea---name");
	tea._sname = "teasname";
	tea._othername = (char *)malloc(sizeof(char) * 128);
	memset(tea._othername,0,sizeof(char) * 128);
	strcpy(tea._othername, "teaothername");
	
	tea._stuname = (char**)malloc(sizeof(char *) * 5);
	memset(tea._stuname, 0, sizeof(char *) * 5);

	for (int i = 0; i < 5; i++)
	{
		tea._stuname[i] = (char *)malloc(sizeof(char) * 128);
		memset(tea._stuname[i], 0, sizeof(char) * 128);
		sprintf(tea._stuname[i], "teastuname%d", i + 100);
	}
	printTea(&tea);

	cout << "---------" << endl;
	Teacher tea1 = tea;
	printTea(&tea1);

	cout << "----1111111111-----" << endl;
	Teacher tea2 ;
	tea2 = tea;
	printTea(&tea2);


	cout << "----2222222222-----" << endl;
	Teacher tea3 = move(tea);
	printTea(&tea3);

	cout << "----333333333-----" << endl;
	Teacher tea4;
	tea4 = move(tea3);
	printTea(&tea4);


	// 原型模式Teacher.clone方法的调用
	Teacher *tea5 = tea4.clone();
	printTea(tea5);
	delete tea5;//这里有些Teacher 的 析构函数,因此delete 会调用析构函数,
}

继承中的代码

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; //具体的实现在子类中进行

	//public:
	//	void setlife(int tmplife)
	//	{
	//		m_life = tmplife;
	//	}
	protected: //可能被子类访问的成员,所以用protected修饰
	//public:
		//怪物属性
		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); //触发拷贝构造函数的调用来创建亡灵类怪物

			/*Monster * pmonster = new M_Undead(300, 50, 80); //创建亡灵类怪物
			//pmonster->m_life = m_life;
			pmonster->setlife(m_life);
			pmonster->m_magic = m_magic;
			pmonster->m_attack = m_attack;
			return pmonster;*/
		}
		//...其他代码略
	};

	//元素类怪物
	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);
		}
		//...其他代码略
	};

	//全局的用于创建怪物对象的函数
	/*void Gbl_CreateMonster2(Monster* pMonster)
	{
		Monster* ptmpobj = nullptr;
		if (dynamic_cast<M_Undead*>(pMonster) != nullptr)
		{
			ptmpobj = new M_Undead(300, 50, 80); //创建亡灵类怪物
		}
		else if (dynamic_cast<M_Element*>(pMonster) != nullptr)
		{
			ptmpobj = new M_Element(200,80, 100); //创建元素类怪物
		}
		else if (dynamic_cast<M_Mechanic*>(pMonster) != nullptr)
		{
			ptmpobj = new M_Mechanic(400, 0, 110); //创建机械类怪物
		}
		if (ptmpobj != nullptr)
		{
			//这里就可以针对ptmpobj对象实现各种业务逻辑
			//......
			//不要忘记释放资源
			delete ptmpobj;
		}
	}*/
	void Gbl_CreateMonster2(Monster* pMonster)
	{
		Monster* ptmpobj = pMonster->clone(); //根据已有对象直接创建新对象,不需要知道已有对象所属的类型
		//这里就可以针对ptmpobj对象实现各种业务逻辑
			//......
			//不要忘记释放资源
		delete ptmpobj;
	}
}

调用

	_nmsp2::M_Mechanic myPropMecMonster(400, 0, 110); //创建一只机械类怪物对象作为原型对象以用于克隆目的
	_nmsp2::Monster* pmyPropEleMonster = new _nmsp2::M_Element(200, 80, 100); //创建一只元素类怪物对象作为原型对象以用于克隆目的,
	                  //这里可以直接new,也可以通过工厂模式创建原型对象,取决于程序员自己的洗好。
	//.....
	_nmsp2::Monster* p_CloneObj1 = myPropMecMonster.clone(); //使用原型对象克隆出新的机械类怪物对象
	_nmsp2::Monster* p_CloneObj2 = pmyPropEleMonster->clone(); //使用原型对象克隆出新的元素类怪物对象


	_nmsp2::Monster* p_CloneObj3 = new _nmsp2::M_Mechanic(myPropMecMonster);

	//可以对p_CloneObj1、p_CloneObj2所指向的对象进行各种操作(实现具体的业务逻辑)
	//......

	//释放资源
	//释放克隆出来的怪物对象
	delete p_CloneObj1;
	delete p_CloneObj2;

	//释放原型对象(堆中的)
	delete pmyPropEleMonster;




	_nmsp2::Monster* pMonsterObj = new _nmsp2::M_Element(200, 80, 100);
	_nmsp2::Gbl_CreateMonster2(pMonsterObj);
	delete pMonsterObj;
相关推荐
唐诺1 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨2 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客3 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin3 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos4 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室5 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0015 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我585 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc5 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很5 小时前
C++ 集合 list 使用
c++