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

news2024/11/6 4:11:38

文章目录

  • 一、引言
  • 二、原型模式
  • 三、总结

一、引言

与工厂模式相同,原型模式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; //clone

protected:  
    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)。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2234040.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于STM32的智能温室环境监测与控制系统设计(代码示例)

一、项目概述 在现代农业中&#xff0c;智能大棚能够通过环境监测、数据分析和自动控制等技术手段&#xff0c;实现对作物生长环境的精细化管理。本项目旨在设计一个基于STM32单片机的智能大棚系统&#xff0c;能够实时监测光照强度、空气温湿度及土壤湿度&#xff0c;并根据设…

(五)Web前端开发进阶2——AJAX

目录 1.Ajax概述 2.Axios库 3.认识URL 4.Axios常用请求方法 5.HTTP协议——请求报文/响应报文 6.HMLHttpRequest对象 7.前后端分离开发&#xff08;接口文档&#xff09; 8.Element组件库 1.Ajax概述 AJAX 是异步的 JavaScript和XML(Asynchronous JavaScript And XML)。…

进程信号——信号的保存

信号的概念 实际执行信号的处理动作称为信号递达(Delivery) 信号从产生到递达之间的状态,称为信号未决(Pending)。 进程可以选择阻塞 (Block )某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作. 注意,阻塞和忽略是不同的,只要信号…

基于SSM的“房屋租赁系统”的设计与实现(源码+数据库+文档+PPT)

基于SSM的“房屋租赁系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM&#xff0c;JSP 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 房屋租赁系统首页 管理员后台管理页面 报告故障管…

无需懂代码!用AI工具Bolt一键生成网站的入门指南!

​ ​ 随着AI技术的不断发展&#xff0c;许多原本需要技术门槛的操作正在被大大简化&#xff0c;甚至零基础的用户也可以轻松实现。 例如&#xff0c;AI生成网站工具Bolt就是这样一个可以帮助我们快速创建、实时预览并自动部署网站的平台。接下来&#xff0c;本文将带你深入了…

Elasticsearch中时间字段格式用法详解

Elasticsearch中时间字段格式用法详解 攻城狮Jozz关注IP属地: 北京 2024.03.18 16:27:51字数 758阅读 2,571 Elasticsearch&#xff08;简称ES&#xff09;是一个基于Lucene构建的开源、分布式、RESTful搜索引擎。它提供了全文搜索、结构化搜索以及分析等功能&#xff0c;广泛…

vue中el-table显示文本过长提示

1.el-table设置轻提示:show-overflow-tooltip“true“&#xff0c;改变轻提示宽度

关于我的编程语言——C/C++——第四篇(深入1)

&#xff08;叠甲&#xff1a;如有侵权请联系&#xff0c;内容都是自己学习的总结&#xff0c;一定不全面&#xff0c;仅当互相交流&#xff08;轻点骂&#xff09;我也只是站在巨人肩膀上的一个小卡拉米&#xff0c;已老实&#xff0c;求放过&#xff09; 字符类型介绍 char…

【春秋云镜】CVE-2023-23752

目录 CVE-2023-23752漏洞细节漏洞利用示例修复建议 春秋云镜&#xff1a;解法一&#xff1a;解法二&#xff1a; CVE-2023-23752 是一个影响 Joomla CMS 的未授权路径遍历漏洞。该漏洞出现在 Joomla 4.0.0 至 4.2.7 版本中&#xff0c;允许未经认证的远程攻击者通过特定 API 端…

AI 写作(一):开启创作新纪元(1/10)

一、AI 写作&#xff1a;重塑创作格局 在当今数字化高速发展的时代&#xff0c;AI 写作正以惊人的速度重塑着创作格局。AI 写作在现代社会中占据着举足轻重的地位&#xff0c;发挥着不可替代的作用。 随着信息的爆炸式增长&#xff0c;人们对于内容的需求日益旺盛。AI 写作能够…

快速构建数据产品原型 —— 我用 VChart Figma 插件

快速构建数据产品原型 —— 我用 VChart Figma 插件 10 种图表类型、24 种内置模板类型、丰富的图表样式配置、自动生成图表实现代码。VChart Figma 插件的目标是提供 便捷好用 & 功能丰富 & 开发友好 的 figma 图表创建能力。目前 VChart 插件功能仍在持续更新中&…

源鲁杯 2024 web(部分)

[Round 1] Disal F12查看: f1ag_is_here.php 又F12可以发现图片提到了robots 访问robots.txt 得到flag.php<?php show_source(__FILE__); include("flag_is_so_beautiful.php"); $a$_POST[a]; $keypreg_match(/[a-zA-Z]{6}/,$a); $b$_REQUEST[b];if($a>99999…

【ArcGIS】绘制各省碳排放分布的中国地图

首先&#xff0c;准备好各省、自治区、直辖市及特别行政区&#xff08;包括九段线&#xff09;的shp文件&#xff1a; 通过百度网盘分享的文件&#xff1a;GS&#xff08;2022&#xff09;1873 链接&#xff1a;https://pan.baidu.com/s/1wq8-XM99LXG_P8q-jNgPJA 提取码&#…

C++《list的模拟实现》

在上一篇C《list》专题当中我们了解了STL当中list类当中的各个成员函数该如何使用&#xff0c;接下来在本篇当中我们将试着模拟实现list&#xff0c;在本篇当中我们将通过模拟实现list过程中深入理解list迭代器和之前学习的vector和string迭代器的不同&#xff0c;接下来就开始…

讲讲⾼可用的原则?

大家好&#xff0c;我是锋哥。今天分享关于【讲讲⾼可用的原则&#xff1f;】面试题。希望对大家有帮助&#xff1b; 讲讲⾼可用的原则&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在当今信息化时代&#xff0c;随着互联网技术的快速发展&#xff0…

003-Kotlin界面开发之声明式编程范式

概念本源 在界面程序开发中&#xff0c;有两个非常典型的编程范式&#xff1a;命令式编程和声明式编程。命令式编程是指通过编写一系列命令来描述程序的运行逻辑&#xff0c;而声明式编程则是通过编写一系列声明来描述程序的状态。在命令式编程中&#xff0c;程序员需要关心程…

Ubuntu 20.04 部署向量数据库 Milvus + Attu

前言 最开始在自己的办公电脑&#xff08;无显卡的 windows 10 系统&#xff09; 上使用 Docker Desktop 部署了 Milvus 容器&#xff0c;方便的很&#xff0c; 下载 Attu 也很方便&#xff0c;直接就把这个向量数据库通过 Attu 这个图形化界面跑了起来&#xff0c;使用起来感…

Linux(inode + 软硬链接 图片+大白话)

后面也会持续更新&#xff0c;学到新东西会在其中补充。 建议按顺序食用&#xff0c;欢迎批评或者交流&#xff01; 缺什么东西欢迎评论&#xff01;我都会及时修改的&#xff01; 在这里真的很感谢这位老师的教学视频让迷茫的我找到了很好的学习视频 王晓春老师的个人空间…

CM API方式设置YARN队列资源

简述 对于CDH版本我们可以参考Fayson的文章,本次是CDP7.1.7 CM7.4.4 ,下面只演示一个设置队列容量百分比的示例,其他请参考cloudera官网。 获取cookies文件 生成cookies.txt文件 curl -i -k -v -c cookies.txt -u admin:admin http://192.168.242.100:7180/api/v44/clusters …

【Linux】简易版shell

文章目录 shell的基本框架PrintCommandLineGetCommandLineParseCommandLineExecuteCommandInitEnvCheckAndExecBuildCommand代码总览运行效果总结 shell的基本框架 要写一个命令行我们首先要写出基本框架。 打印命令行获取用户输入的命令分析命令执行命令 基本框架的代码&am…