设计模式:原型模式

引言

本篇文章主要是关于原型模式和适配器模式,通过理论和代码结合的方式。

原型模式

简介

原型模式用最简单的话说明就是复制拷贝,比如我们在写一个游戏的时候要创建一个副本,我们可以把主线的人物拷贝到副本,然后做一点小小的修改,这样就不必要从零开始搭建。所以我们一般要一个原型的对象。

我们之所以要这个模式,是因为创建一个对象需要很大的资源消耗,所以我们可以通过复制现有对象的方式来快速创建一个新的对象。这个模型我们需要一个抽象的原接口prototype,声明一个自身的clone(),然后就是具体的原型类ConcretePrototype(),实现了clone(),这个方法里面就是复制并返回了一个新的对象。

这个模型真正难得地方并不是怎么复制,而是复制到什么程度。我们c++里面涉及到了两个知识点:深拷贝和浅拷贝。简单来说就是对于指针得拷贝,浅拷贝是共用地址,深拷贝是创建了一个新的地址。

代码

浅拷贝

首先我们先创建一个Prototype,这个里面有clone()的方法,方便后面继承。然后还有一个析构函数,因为子类要析构那么基类也一定要有析构函数。

cpp 复制代码
#pragma once
#ifndef _PROTOTYPE_H_
#define _PROTOTYPE_H_

namespace proto {
	class Prototype {
	public:
		virtual Prototype* clone() = 0;
		virtual ~Prototype() {} // 确保子类可以正确释放资源
	};
}
#endif

我们一个关卡里面有奖励还有怪兽

cpp 复制代码
#pragma once
#include "../../../Macros.h"
#ifndef _REWARDS_H_
#define _REWARDS_H_

namespace proto {
	class Rewards {
		CC_SYNTHESIZE(int, m_gold, Gold);
	public:
		Rewards(int gold) : m_gold(gold) {}
	};
}
#endif
cpp 复制代码
#pragma once
#ifndef _MONSTER_H_
#define _MONSTER_H_

#include "../Prototype.h"
#include "../../../Macros.h"
#include <string>

namespace proto {
	class Monster {
		CC_SYNTHESIZE(std::string, m_name, Name);
		CC_SYNTHESIZE(int, m_hp, HP);
		CC_SYNTHESIZE(int, m_attack, Attack);
	public:
		Monster(std::string name, int hp, int attack) : m_name(name), m_hp(hp), m_attack(attack) {}
	};
}
#endif

然后就是我们需要复制的关卡,所以要继承Portotype类,并且重写clone(),不过大家应该也注意到了我们还有一个变量 m_autoRelease,因为这个是浅拷贝,所以浅拷贝的特性就是对于指针的拷贝只是共用一个地址,那么当原型被delete之后,指针就已经被释放了,所以当副本要重新释放的时候就会释放空指针报错,所以我们需要对浅拷贝的类单独写属于他的析构函数,

cpp 复制代码
#pragma once
#include "../Prototype.h"
#include <string>
#include "../../../Macros.h"
#include <vector>
#include "Monster.h"
#include "Rewards.h"
#ifndef _LEVEL_H_
#define _LEVEL_H_

namespace proto {
	class Level : public Prototype { 
		CC_SYNTHESIZE(std::string, m_name, Name);
		CC_SYNTHESIZE(std::string, m_desc, Descript);
		CC_SYNTHESIZE_CR_GET(std::vector<Monster*>, m_monsters, Monsters);
		CC_SYNTHESIZE(Rewards*, m_rewards, Rewards);
		CC_SYNTHESIZE(bool, m_autoRelease, AutoRelease);
	public:
		Prototype* clone() override;
		Level();
		virtual ~Level();
		void printInfo();
	};
};

#endif

首先对于克隆函数,我们直接是拿着现在的复制一个新的,这也就是浅拷贝,但是我们千万不要直接返回,因为我们还要修改一下 m_autoRelease变量,不要自动的释放了。不过对于构造函数,也就是原型来说是可以直接释放的,所以在构造函数里面赋值为true,析构函数对应的就是原函数亲自释放指针,如果是副本那么就是return,

cpp 复制代码
#include "Level.h"
#include <iostream>



proto::Prototype* proto::Level::clone()
{
	// 使用拷贝构造进行克隆
	auto object = new Level(*this);
	object->m_autoRelease = false;
	return object;
}

proto::Level::Level()
{
	this->m_rewards = nullptr;
	this->m_autoRelease = true; // 默认不自动释放资源; 
}

proto::Level::~Level()
{
	if (!m_autoRelease) {
		return;
	}
	if (m_rewards != nullptr) {
		delete m_rewards; // 释放奖励资源
		m_rewards = nullptr; // 确保指针为空
	}
	for (auto monster : m_monsters) {
		delete monster; // 释放怪物资源
	}
	m_monsters.clear(); // 清空怪物列表
}

void proto::Level::printInfo()
{
	std::cout << "Level Name: " << m_name << std::endl;
	std::cout << "Level Description: " << m_desc << std::endl;
	std::cout << "Monsters:" << std::endl;
	for (auto monster : m_monsters) {
		std::cout << "  Name: " << monster->getName() << ", HP: " << monster->getHP() << ", Attack: " << monster->getAttack() << std::endl;
	}
	std::cout << "Rewards: " << m_rewards->getGold() << " gold" << std::endl;
}

最后就是我们的客户端,先创建一个原型,给原型赋值,然后利用抽象类Prototype克隆

cpp 复制代码
#include "level/Level.h"
#include <iostream>
#include "level/Monster.h"
using namespace proto;
int main() {
	// 创建一个原型实例
	Level* proto = new Level();
	proto->setName("哥布林");
	proto->setDescript("这是一个充满哥布林的房间");
	proto->getMonsters().emplace_back(new Monster("普通哥布林", 100, 50));
	proto->getMonsters().emplace_back(new Monster("投石哥布林", 200, 100));
	proto->setRewards(new Rewards(500));

	Level* clone = (Level*)proto->clone();

	proto->printInfo();
	clone->printInfo();
	clone->getRewards()->setGold(1000);
	clone->printInfo();
	
	delete proto;
	delete clone;
	return 0;
}

所以对于浅拷贝来说最重要的就是内存的释放。

深拷贝

和浅拷贝相比,深拷贝的区别就是要一个完全一摸一样的副本,并且不要和原型共用一个地址,所以我们需要有一个私有的copy(),这个函数的目的就是一个一个值得创建拷贝,然后我们之后无论是拷贝构造还是赋值构造,底层都是copy,所以最后我们也需要一个单独的release来管理已经创建的类,原型析构的时候同时调用release()。

cpp 复制代码
#pragma once
#include "../Prototype.h"
#include <string>
#include "../../../Macros.h"
#include <vector>
#include "Monster.h"
#include "Rewards.h"
#ifndef _LEVEL_H_
#define _LEVEL_H_

namespace proto {
	class Level : public Prototype { 
		CC_SYNTHESIZE(std::string, m_name, Name);
		CC_SYNTHESIZE(std::string, m_desc, Descript);
		CC_SYNTHESIZE_CR_GET(std::vector<Monster*>, m_monsters, Monsters);
		CC_SYNTHESIZE(Rewards*, m_rewards, Rewards);
	private:
		void copy(const Level& other);
		void release();
	public:
		Prototype* clone() override;
		Level();
		Level(const Level& other);
		Level operator=(const Level& other);
		virtual ~Level();
		void printInfo();
	};
};

#endif

代码里面无论是clone()用的是拷贝构造,而无论是拷贝构造还是赋值构造的底层都是copy(),不过我们要注意一个是赋值构造的时候一定要先进行释放,因为有可能有之前的性质已经new过了,但是因为时机没有到还没有释放,所以当我们利用之前的模型再次copy()的时候又会创建一个新的,那就会new出两个。

cpp 复制代码
#include "Level.h"
#include <iostream>

void proto::Level::copy(const Level& other)
{
	this->m_name = other.m_name;
	this->m_desc = other.m_desc;
	if (other.m_rewards != nullptr) {
		this->m_rewards = new Rewards(*other.m_rewards);
	}
	for (auto monster : other.m_monsters) {
		this->m_monsters.emplace_back(new Monster(*monster));
	}
}

void proto::Level::release()
{
	if (m_rewards != nullptr) {
		delete m_rewards; // 释放奖励资源
		m_rewards = nullptr; // 确保指针为空
	}
	for (auto monster : m_monsters) {
		delete monster; // 释放怪物资源
	}
	m_monsters.clear(); // 清空怪物列表
}

proto::Prototype* proto::Level::clone()
{
	// 使用拷贝构造进行克隆
	return new Level(*this);
}

proto::Level::Level()
{
	this->m_rewards = nullptr;
}

proto::Level proto::Level::operator=(const Level& other)
{
	// 先释放内存
	release();
	// 再执行拷贝
	copy(other);
	return *this;
}

proto::Level::Level(const Level& other)
{
	copy(other);
}

proto::Level::~Level()
{
	release();
}

void proto::Level::printInfo()
{
	std::cout << "Level Name: " << m_name << std::endl;
	std::cout << "Level Description: " << m_desc << std::endl;
	std::cout << "Monsters:" << std::endl;
	for (auto monster : m_monsters) {
		std::cout << "  Name: " << monster->getName() << ", HP: " << monster->getHP() << ", Attack: " << monster->getAttack() << std::endl;
	}
	std::cout << "Rewards: " << m_rewards->getGold() << " gold" << std::endl;
}

总结

这是一个很重要的设计模式,因为在很多情况下我们需要赋值一个几乎一样的对象。

本篇文章到这里就结束了!!!希望可以帮助大家理解~~~~

相关推荐
郝学胜-神的一滴1 小时前
Qt 高级编程 037:QSS按钮样式封神指南
开发语言·c++·qt·软件工程·用户界面
霍霍的袁2 小时前
【C++】string类 从 入门使用 到 底层深浅拷贝模拟实现
开发语言·c++·visual studio
董员外2 小时前
RAG 系统进化论(五):Corrective RAG 与 Self-RAG,让系统发现并纠正错误
人工智能·后端·设计模式
noipp3 小时前
推荐题目:洛谷 P3726 [AHOI2017/HNOI2017] 抛硬币
c语言·数据结构·c++·算法·编程·洛谷·luogu
c238563 小时前
C/C++每日一练14
c语言·c++·算法
影视飓风TIM3 小时前
C++ STL 完整入门笔记[8]:deque 底层原理深度剖析
开发语言·c++·笔记
zmzb01033 小时前
C++课后习题训练记录Day176
开发语言·c++
qz5zwangzihan13 小时前
题解:Atcoder Beginner Contest abc469 F - GCD Maximum Spanning Tree
c++·图论·kruskal·并查集·atcoder·abc469·abc469_f
王维同学3 小时前
跨位数进程的映像、命令行与环境块读取
c++·windows·安全