C++设计模式创建型模式———原型模式
文章目录
- 一、引言
- 二、原型模式
- 三、总结
一、引言
与工厂模式相同,原型模式(Prototype)也是创建型模式。原型模式通过一个对象(原型对象)克隆出多个一模一样的对象。实际上,该模式与其说是一种设计模式,不如说是一种创建对象的方法(对象克隆),尤其是创建给定类的对象(实例)过程很复杂(例如,要设置许多成员变量的值)时,使用这种设计模式就比较合适。
二、原型模式
也就是说,原型模式是为了使你能够复制已有对象, 而又无需使代码依赖它们所属的类。
如果你有一个对象, 并希望生成与其完全相同的一个复制品, 你该如何实现呢? 首先, 你必须新建一个属于相同类的对象。 然后, 你必须遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。
不错! 但有个小问题。 并非所有对象都能通过这种方式进行复制, 因为有些对象可能拥有私有成员变量, 它们在对象本身以外是不可见的。直接复制还有另外一个问题。 因为你必须知道对象所属的类才能创建复制品, 所以代码必须依赖该类。 即使你可以接受额外的依赖性, 那还有另外一个问题: 有时你只知道对象所实现的接口, 而不知道其所属的具体类, 比如可向方法的某个参数传入实现了某个接口的任何对象。克隆可能会在父类和子类之间进行,并且可能是动态的,很明显通过父类的拷贝构造函数无法实现对子类对象的拷贝,其实这就是一个多态,我们需要给父类提供一个克隆函数并且是一个虚函数。
仍然使用闯关打怪兽的案例来解释。下面是一个怪兽类。我们想让怪物父类拥有clone自己的能力。
// 怪物父类
class Monster {
public:// 构造函数Monster(int life, int magic, int attack): m_life(life), m_magic(magic), m_attack(attack) {}virtual ~Monster() {} // 虚析构函数virtual unique_ptr<Monster> clone() const = 0; //cloneprotected: int m_life; // 生命值int m_magic; // 魔法值int m_attack; // 攻击力
};
clone函数意味着调用该成员函数就会从当前类对象复制出一个完全相同的对象(通过克隆自已来创建出新对象),这当然也是一种创建该类所属对象的方式。这三种怪物实现父类的clone方法。
// 亡灵类
class M_Undead : public Monster {
public:M_Undead(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只亡灵类怪物来到了这个世界" << endl;}unique_ptr<Monster> clone() const override {cout<<" 亡灵类被克隆了 "<<endl;return make_unique<M_Undead>(*this); // 克隆自身}
};// 元素类
class M_Element : public Monster {
public:M_Element(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只元素类怪物来到了这个世界" << endl;}unique_ptr<Monster> clone() const override {cout<<" 元素类被克隆了 "<<endl;return make_unique<M_Element>(*this); // 克隆自身}
};// 机械类
class M_Mechanic : public Monster {
public:M_Mechanic(int life, int magic, int attack) : Monster(life, magic, attack) {cout << "一只机械类怪物来到了这个世界" << endl;}unique_ptr<Monster> clone() const override {cout<<" 机械类被克隆了 "<<endl;return make_unique<M_Mechanic>(*this); // 克隆自身}
};
既然是克隆,那么上述M_Undead、M_Element、M_Mechanic中的clone成员函数的实现体是需要修改的。例如,某个机械类怪物因为被主角砍了一刀失去了100点生命值,导致该怪物对象的m_life成员变量(生命值)从原来的400变成300,那么调用clone方法克隆出来的新机械类怪物对象也应该是300点生命值,所以此时M_Mechanic类中clone成员函数中的代码行return new M_Mechanic(400,0,110);就不合适,因为这样会创建(克隆)出一个400点生命值的新怪物,不符合clone这个成员函数的本意(复制出一个完全相同的对象)。
克隆对象自身实际上是需要调用类的拷贝构造函数的。如果程序员在类中没有定义自已的拷贝构造函数,那么编译器会在必要的时候(但不是一定)合成出一个拷贝构造函数。因此,**在使用原型模式的时候要注意深拷贝和浅拷贝的问题。**下面添加拷贝构造函数。
// 亡灵类
// 拷贝构造函数
M_Undead::M_Undead(const M_Undead& other) : Monster(other) {cout << "亡灵类被拷贝了" << endl;
}
为了方便,我们仅写一个。这里我们需要确保能正确编写拷贝构造函数,这样调用clone才能正确的克隆出对象。
unique_ptr<Monster> undead = make_unique<M_Undead>(100, 50, 20);
unique_ptr<Monster> undeadClone = undead->clone(); // 克隆亡灵怪物unique_ptr<Monster> element = make_unique<M_Element>(80, 70, 30);
unique_ptr<Monster> elementClone = element->clone(); // 克隆元素怪物unique_ptr<Monster> mechanic = make_unique<M_Mechanic>(120, 40, 50);
unique_ptr<Monster> mechanicClone = mechanic->clone(); // 克隆机械怪物
原型模式将克隆过程委派给被克隆的实际对象。 模式为所有支持克隆的对象声明了一个通用接口, 该接口能够克隆对象, 同时又无需将代码和对象所属类耦合。 通常情况下, 这样的接口中仅包含一个
clone方法。所有的类对
clone方法的实现都非常相似。 该方法会创建一个当前类的对象, 然后将原始对象所有的成员变量值复制到新建的类中。 甚至可以复制私有成员变量, 因为绝大部分编程语言都允许对象访问其同类对象的私有成员变量。支持克隆的对象即为原型。 当对象有几十个成员变量和几百种类型时, 对其进行克隆甚至可以代替子类的构造。
原型模式就是能够复制已有的对象,而又无需使代码依赖它们所属的类。换种说法,就是通过已有对象克隆出另一个新的对象,并且克隆这个对象不需要使用构造函数。

原型模式的UML图中,包含两种角色。
- 抽象原型类(Prototype):所有具体原型类的父类,在其中声明克隆方法。这里指Monster类。
- 具体原型类(oncretePrototype):实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。这里指M_Undead类、M_Element类和M_Mechanic类。
引入原型模型的定义:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。简单来说,就是通过克隆来创建新的对象实例。
原型模式结构

- 原型 (Prototype) 接口将对克隆方法进行声明。 在绝大多数情况下, 其中只会有一个名为
clone克隆的方法。 - 具体原型 (Concrete Prototype) 类将实现克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等。
- 客户端 (Client) 可以复制实现了原型接口的任何对象。

原型注册表 (Prototype Registry) 提供了一种访问常用原型的简单方法, 其中存储了一系列可供随时复制的预生成对象。 最简单的注册表原型是一个 名称 → 原型的哈希表。 但如果需要使用名称以外的条件进行搜索, 你可以创建更加完善的注册表版本。
三、总结
原型模式与工厂方法模式在创建对象时的主要区别在于它们如何处理对象的创建过程和状态复制。
原型模式通过复制现有对象(原型)来创建新对象,新对象的初始状态与原型对象相同,这避免了复杂的设置过程。当对象的内部数据复杂且多变时,原型模式比工厂方法模式更合适,因为它可以直接克隆当前状态,无需额外的设置代码。例如,在游戏中创建一个具有特定状态的怪物分身,使用原型模式可以快速复制这些状态。
工厂方法模式和原型模式在创建对象时都不需要知道具体的类名,但它们的工作方式不同。工厂方法模式通过调用创建接口来创建新对象,而原型模式通过克隆现有对象。如果对象的创建成本较高,或者需要避免复杂的初始化逻辑,原型模式是一个更好的选择。总结来说,两种模式都能解耦对象的创建过程,但原型模式在处理动态和复杂状态的对象时更为高效。
因此,如果对象的内部数据比较复杂且多变并且在创建对象的时候希望保持对象的当前状态,那么用原型模式显然比原型模式更合适。
工厂方法模式与原型模式在创建对象时的异同点:
- 前面范例中创建怪物对象时,这两种模式其实都不需要程序员知道所创建对象所属的类名;
- 工厂方法模式是调用相应的创建接口,例如使用
createMonster接口来创建新的怪物对象,该接口中采用代码行``new类名(参数)`来完成对象的最终创建工作,这仍旧是属于根据类名来生成新对象; - 型模式是调用例如
clone(程序员可以修改成任意其他名字)接口来创建新的怪物对象,按照惯例,这个接口一般不带任何参数,以免破坏克隆接口的统一性。该接口中采用的是代码行new类名(*this)完成对类拷贝构造函数的调用来创建对象,所以这种创建对象的方式是根据现有对象来生成新对象
当然,也可以把原型模式看成是一种特殊的工厂方法模式(工厂方法模式的变体),这也是可以的一把原型对象所属的类本身(例如,M_Undead、M_Element、M_Mechanic)看成是创建克隆对象的工厂,而工厂方法指的自然就是克隆方法(clone)。
有时候原型可以作为备忘录模式的一个简化版本, 其条件是需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。
在大量使用组合模式和装饰模式的设计时,可以通过原型模式来复制复杂结构, 而非从零开始重新构造。
相关文章:
C++设计模式创建型模式———原型模式
文章目录 一、引言二、原型模式三、总结 一、引言 与工厂模式相同,原型模式(Prototype)也是创建型模式。原型模式通过一个对象(原型对象)克隆出多个一模一样的对象。实际上,该模式与其说是一种设计模式&am…...
重学SpringBoot3-Spring WebFlux之SSE服务器发送事件
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ Spring WebFlux之SSE服务器发送事件 1. 什么是 SSE?2. Spring Boot 3 响应式编程与 SSE为什么选择响应式编程实现 SSE? 3. 实现 SSE 的基本步骤3.…...
YOLO即插即用模块---AgentAttention
Agent Attention: On the Integration of Softmax and Linear Attention 论文地址:https://arxiv.org/pdf/2312.08874 问题: 普遍使用的 Softmax 注意力机制在视觉 Transformer 模型中计算复杂度过高,限制了其在各种场景中的应用。 方法&a…...
探索开源语音识别的未来:高效利用先进的自动语音识别技术20241030
🚀 探索开源语音识别的未来:高效利用自动语音识别技术 🌟 引言 在数字化时代,语音识别技术正在引领人机交互的新潮流,为各行业带来了颠覆性的改变。开源的自动语音识别(ASR)系统,如…...
学习路之TP6--workman安装
一、安装 首先通过 composer 安装 composer require topthink/think-worker 报错: 分析:最新版本需要TP8,或装低版本的 composer require topthink/think-worker:^3.*安装后, 增加目录 vendor\workerman vendor\topthink\think-w…...
.NET内网实战:通过白名单文件反序列化漏洞绕过UAC
01阅读须知 此文所节选自小报童《.NET 内网实战攻防》专栏,主要内容有.NET在各个内网渗透阶段与Windows系统交互的方式和技巧,对内网和后渗透感兴趣的朋友们可以订阅该电子报刊,解锁更多的报刊内容。 02基本介绍 03原理分析 在渗透测试和红…...
AI Agents - 自动化项目:计划、评估和分配
Agents: Role 角色Goal 目标Backstory 背景故事 Tasks: Description 描述Expected Output 期望输出Agent 代理 Automated Project: Planning, Estimation, and Allocation Initial Imports 1.本地文件helper.py # Add your utilities or helper functions to…...
Git的.gitignore文件
一、各语言对应的.gitignore模板文件 项目地址:https://github.com/github/gitignore 二、.gitignore文件不生效 .gitignore文件只是ignore没有被追踪的文件,已被追踪的文件,要先删除缓存文件。 # 单个文件 git rm --cached file/path/to…...
网站安全,WAF网站保护暴力破解
雷池的核心功能 通过过滤和监控 Web 应用与互联网之间的 HTTP 流量,功能包括: SQL 注入保护:防止恶意 SQL 代码的注入,保护网站数据安全。跨站脚本攻击 (XSS):阻止攻击者在用户浏览器中执行恶意脚本。暴力破解防护&a…...
深度学习:梯度下降算法简介
梯度下降算法简介 梯度下降算法 我们思考这样一个问题,现在需要用一条直线来回归拟合这三个点,直线的方程是 y w ^ x b y \hat{w}x b yw^xb,我们假设斜率 w ^ \hat{w} w^是已知的,现在想要找到一个最好的截距 b b b。 一条…...
SparkSQL整合Hive后,如何启动hiveserver2服务
当spark sql与hive整合后,我们就无法启动hiveserver2的服务了,每次都要先启动hive的元数据服务(nohup hive --service metastore)才能启动hive,之前的beeline命令也用不了,hiveserver2的无法启动,这也导致我…...
前端路由如何从0开始配置?vue-router 的使用
在 Web 开发中,路由是指根据 URL 的不同部分将请求分发到不同的处理函数或页面的过程。路由是单页应用(SPA, Single Page Application)和服务器端渲染(SSR, Server-Side Rendering)应用中的一个重要概念。 在开发中如何…...
Java中的运算符【与C语言的区别】
目录 1. 算术运算符 1.0 赋值运算符: 1.1 四则运算符: - * / % 【取余与C有点不同】 1.2 增量运算符: - * / % * 【右侧运算结果会自动转换类型】 1.3 自增、自减:、-- 2. 关系运算符 3. 逻辑运算符 3.1 短路求值 3.2 【…...
二、基础语法
入门了解 注释 **作用:**在代码中加一些注释和说明,方便自己或者其他程序员阅读代码 两种格式: 单行注释:// 描述信息 通常放在一行代码的上方,或者一条语句的末尾,对该行代码进行说明 多行注释&#x…...
DB-GPT系列(一):DB-GPT能帮你做什么?
DB-GPT是一个开源的AI原生数据应用开发框架(AI Native Data App Development framework with AWEL and Agents),围绕大模型提供灵活、可拓展的AI原生数据应用管理与开发能力,可以帮助企业快速构建、部署智能AI数据应用,通过智能数据分析、洞察…...
【Python各个击破】numpy
简介 NumPy是一个开源的Python库,它提供了一个强大的N维数组对象和许多用于操作这些数组的函数。它是大多数Python科学计算的基础,包括Pandas、SciPy和scikit-learn等库都建立在NumPy之上。 安装 !pip install numpy导入 import numpy as np用法 # …...
【STM32 Blue Pill编程实例】-4位7段数码管使用
4位7段数码管使用 文章目录 4位7段数码管使用1、7段数码介绍2、硬件准备与接线3、模块配置4、代码实现在本文中,我们将介绍如何将 STM32 Blue Pill开发板与 4 位 7 段数码管连接,并在 STM32CubeIDE 中对其进行编程。 在文章中首先将介绍 4 位 7 段数码管及其与 STM32 Blue Pi…...
[进阶]java基础之集合(三)数据结构
文章目录 数据结构概述常见的数据结构数据结构(栈)数据结构(队列)数据结构(数组)数据结构(链表) 数据结构 概述 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。数据结构是为了更加方便的管理和使用数据,需要结合具体的业…...
《Apache Cordova/PhoneGap 使用技巧分享》
一、引言 在移动应用开发的领域中,Apache Cordova(也被称为 PhoneGap)是一个强大的工具,它允许开发者使用 HTML、CSS 和 JavaScript 等 Web 技术来构建跨平台的移动应用。这种方式不仅能够提高开发效率,还能降低开发成…...
SCP(Secure Copy
SCP(Secure Copy)是Linux系统下基于SSH协议的安全文件传输工具,用于在本地和远程主机间安全、快速地传输文件和目录。SCP命令通过加密传输确保数据的安全性,并且不占用过多系统资源。 SCP的基本用法 基本语法:…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
