设计模式:原型模式

引言

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

原型模式

简介

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

我们之所以要这个模式,是因为创建一个对象需要很大的资源消耗,所以我们可以通过复制现有对象的方式来快速创建一个新的对象。这个模型我们需要一个抽象的原接口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;
}

总结

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

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

相关推荐
C++ 老炮儿的技术栈17 分钟前
机器人的坐标,像 XYZ 的坐标值,是用单精度浮点还是用双精度浮点?
c语言·c++·人工智能·单片机·c
zaizai100719 分钟前
STL深度剖析2
java·c++·算法
幻智星科技26 分钟前
FPGA纯硬件UDP桥接:微秒级响应新范式
c++·fpga开发·硬件架构
青少儿编程课堂31 分钟前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:位运算实战——二进制权限与最大异或解析
c++·python·算法·bfs·信息学竞赛
青少儿编程课堂34 分钟前
2026蓝桥杯青少组图形化真题:校园飞机大战解析(全国青少年信息素养·电子学会图形化考级)
c++·python·算法·bfs·信息学竞赛
CodeYp20191 小时前
第16讲:set 和 map 的使用
开发语言·c++·set·map
傲世仙尊1 小时前
vector起步与底层-三个指针迭代器失效与为模板升级内置类型
c++
是2的10次方啊1 小时前
设计模式与生活④:冰箱空了,早餐换个买法
设计模式
誰能久伴不乏3 小时前
桌面客户端的运行日志系统:SQLite 分块 + 独立写线程 + 文本降级
数据库·c++·qt·架构·sqlite
知守观12 小时前
设计模式实战:从真实老项目中看标准模板方法与工厂的落地
java·spring·设计模式