当前位置: 首页 > news >正文

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_UndeadM_ElementM_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类。

引入原型模型的定义:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。简单来说,就是通过克隆来创建新的对象实例。

原型模式结构

在这里插入图片描述

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

在这里插入图片描述

原型注册表 (Prototype Registry) 提供了一种访问常用原型的简单方法, 其中存储了一系列可供随时复制的预生成对象。 最简单的注册表原型是一个 名称 → 原型的哈希表。 但如果需要使用名称以外的条件进行搜索, 你可以创建更加完善的注册表版本。


三、总结

原型模式与工厂方法模式在创建对象时的主要区别在于它们如何处理对象的创建过程和状态复制。

原型模式通过复制现有对象(原型)来创建新对象,新对象的初始状态与原型对象相同,这避免了复杂的设置过程。当对象的内部数据复杂且多变时,原型模式比工厂方法模式更合适,因为它可以直接克隆当前状态,无需额外的设置代码。例如,在游戏中创建一个具有特定状态的怪物分身,使用原型模式可以快速复制这些状态。

工厂方法模式和原型模式在创建对象时都不需要知道具体的类名,但它们的工作方式不同。工厂方法模式通过调用创建接口来创建新对象,而原型模式通过克隆现有对象。如果对象的创建成本较高,或者需要避免复杂的初始化逻辑,原型模式是一个更好的选择。总结来说,两种模式都能解耦对象的创建过程,但原型模式在处理动态和复杂状态的对象时更为高效。

因此,如果对象的内部数据比较复杂且多变并且在创建对象的时候希望保持对象的当前状态,那么用原型模式显然比原型模式更合适。

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

  • 前面范例中创建怪物对象时,这两种模式其实都不需要程序员知道所创建对象所属的类名;
  • 工厂方法模式是调用相应的创建接口,例如使用createMonster接口来创建新的怪物对象,该接口中采用代码行``new类名(参数)`来完成对象的最终创建工作,这仍旧是属于根据类名来生成新对象;
  • 型模式是调用例如clone(程序员可以修改成任意其他名字)接口来创建新的怪物对象,按照惯例,这个接口一般不带任何参数,以免破坏克隆接口的统一性。该接口中采用的是代码行new类名(*this)完成对类拷贝构造函数的调用来创建对象,所以这种创建对象的方式是根据现有对象来生成新对象

当然,也可以把原型模式看成是一种特殊的工厂方法模式(工厂方法模式的变体),这也是可以的一把原型对象所属的类本身(例如,M_Undead、M_Element、M_Mechanic)看成是创建克隆对象的工厂,而工厂方法指的自然就是克隆方法(clone)。

有时候原型可以作为备忘录模式的一个简化版本, 其条件是需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。

在大量使用组合模式和装饰模式的设计时,可以通过原型模式来复制复杂结构, 而非从零开始重新构造。

相关文章:

C++设计模式创建型模式———原型模式

文章目录 一、引言二、原型模式三、总结 一、引言 与工厂模式相同&#xff0c;原型模式&#xff08;Prototype&#xff09;也是创建型模式。原型模式通过一个对象&#xff08;原型对象&#xff09;克隆出多个一模一样的对象。实际上&#xff0c;该模式与其说是一种设计模式&am…...

重学SpringBoot3-Spring WebFlux之SSE服务器发送事件

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ Spring WebFlux之SSE服务器发送事件 1. 什么是 SSE&#xff1f;2. Spring Boot 3 响应式编程与 SSE为什么选择响应式编程实现 SSE&#xff1f; 3. 实现 SSE 的基本步骤3.…...

YOLO即插即用模块---AgentAttention

Agent Attention: On the Integration of Softmax and Linear Attention 论文地址&#xff1a;https://arxiv.org/pdf/2312.08874 问题&#xff1a; 普遍使用的 Softmax 注意力机制在视觉 Transformer 模型中计算复杂度过高&#xff0c;限制了其在各种场景中的应用。 方法&a…...

探索开源语音识别的未来:高效利用先进的自动语音识别技术20241030

&#x1f680; 探索开源语音识别的未来&#xff1a;高效利用自动语音识别技术 &#x1f31f; 引言 在数字化时代&#xff0c;语音识别技术正在引领人机交互的新潮流&#xff0c;为各行业带来了颠覆性的改变。开源的自动语音识别&#xff08;ASR&#xff09;系统&#xff0c;如…...

学习路之TP6--workman安装

一、安装 首先通过 composer 安装 composer require topthink/think-worker 报错&#xff1a; 分析&#xff1a;最新版本需要TP8&#xff0c;或装低版本的 composer require topthink/think-worker:^3.*安装后&#xff0c; 增加目录 vendor\workerman vendor\topthink\think-w…...

.NET内网实战:通过白名单文件反序列化漏洞绕过UAC

01阅读须知 此文所节选自小报童《.NET 内网实战攻防》专栏&#xff0c;主要内容有.NET在各个内网渗透阶段与Windows系统交互的方式和技巧&#xff0c;对内网和后渗透感兴趣的朋友们可以订阅该电子报刊&#xff0c;解锁更多的报刊内容。 02基本介绍 03原理分析 在渗透测试和红…...

AI Agents - 自动化项目:计划、评估和分配

Agents: Role 角色Goal 目标Backstory 背景故事 Tasks&#xff1a; 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模板文件 项目地址&#xff1a;https://github.com/github/gitignore 二、.gitignore文件不生效 .gitignore文件只是ignore没有被追踪的文件&#xff0c;已被追踪的文件&#xff0c;要先删除缓存文件。 # 单个文件 git rm --cached file/path/to…...

网站安全,WAF网站保护暴力破解

雷池的核心功能 通过过滤和监控 Web 应用与互联网之间的 HTTP 流量&#xff0c;功能包括&#xff1a; SQL 注入保护&#xff1a;防止恶意 SQL 代码的注入&#xff0c;保护网站数据安全。跨站脚本攻击 (XSS)&#xff1a;阻止攻击者在用户浏览器中执行恶意脚本。暴力破解防护&a…...

深度学习:梯度下降算法简介

梯度下降算法简介 梯度下降算法 我们思考这样一个问题&#xff0c;现在需要用一条直线来回归拟合这三个点&#xff0c;直线的方程是 y w ^ x b y \hat{w}x b yw^xb&#xff0c;我们假设斜率 w ^ \hat{w} w^是已知的&#xff0c;现在想要找到一个最好的截距 b b b。 一条…...

SparkSQL整合Hive后,如何启动hiveserver2服务

当spark sql与hive整合后&#xff0c;我们就无法启动hiveserver2的服务了&#xff0c;每次都要先启动hive的元数据服务&#xff08;nohup hive --service metastore&#xff09;才能启动hive,之前的beeline命令也用不了&#xff0c;hiveserver2的无法启动&#xff0c;这也导致我…...

前端路由如何从0开始配置?vue-router 的使用

在 Web 开发中&#xff0c;路由是指根据 URL 的不同部分将请求分发到不同的处理函数或页面的过程。路由是单页应用&#xff08;SPA, Single Page Application&#xff09;和服务器端渲染&#xff08;SSR, Server-Side Rendering&#xff09;应用中的一个重要概念。 在开发中如何…...

Java中的运算符【与C语言的区别】

目录 1. 算术运算符 1.0 赋值运算符&#xff1a; 1.1 四则运算符&#xff1a; - * / % 【取余与C有点不同】 1.2 增量运算符&#xff1a; - * / % * 【右侧运算结果会自动转换类型】 1.3 自增、自减&#xff1a;、-- 2. 关系运算符 3. 逻辑运算符 3.1 短路求值 3.2 【…...

二、基础语法

入门了解 注释 **作用&#xff1a;**在代码中加一些注释和说明&#xff0c;方便自己或者其他程序员阅读代码 两种格式&#xff1a; 单行注释&#xff1a;// 描述信息 通常放在一行代码的上方&#xff0c;或者一条语句的末尾&#xff0c;对该行代码进行说明 多行注释&#x…...

DB-GPT系列(一):DB-GPT能帮你做什么?

DB-GPT是一个开源的AI原生数据应用开发框架(AI Native Data App Development framework with AWEL and Agents)&#xff0c;围绕大模型提供灵活、可拓展的AI原生数据应用管理与开发能力&#xff0c;可以帮助企业快速构建、部署智能AI数据应用&#xff0c;通过智能数据分析、洞察…...

【Python各个击破】numpy

简介 NumPy是一个开源的Python库&#xff0c;它提供了一个强大的N维数组对象和许多用于操作这些数组的函数。它是大多数Python科学计算的基础&#xff0c;包括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基础之集合(三)数据结构

文章目录 数据结构概述常见的数据结构数据结构(栈)数据结构(队列)数据结构(数组)数据结构(链表) 数据结构 概述 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。数据结构是为了更加方便的管理和使用数据&#xff0c;需要结合具体的业…...

《Apache Cordova/PhoneGap 使用技巧分享》

一、引言 在移动应用开发的领域中&#xff0c;Apache Cordova&#xff08;也被称为 PhoneGap&#xff09;是一个强大的工具&#xff0c;它允许开发者使用 HTML、CSS 和 JavaScript 等 Web 技术来构建跨平台的移动应用。这种方式不仅能够提高开发效率&#xff0c;还能降低开发成…...

SCP(Secure Copy

SCP&#xff08;Secure Copy&#xff09;‌是Linux系统下基于SSH协议的安全文件传输工具&#xff0c;用于在本地和远程主机间安全、快速地传输文件和目录。SCP命令通过加密传输确保数据的安全性&#xff0c;并且不占用过多系统资源‌。 SCP的基本用法 ‌基本语法‌&#xff1a…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

Spring Boot 实现流式响应(兼容 2.7.x)

在实际开发中&#xff0c;我们可能会遇到一些流式数据处理的场景&#xff0c;比如接收来自上游接口的 Server-Sent Events&#xff08;SSE&#xff09; 或 流式 JSON 内容&#xff0c;并将其原样中转给前端页面或客户端。这种情况下&#xff0c;传统的 RestTemplate 缓存机制会…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...