原型模式(Prototype)

news2024/10/6 22:34:01

定义

原型是一种创建型设计模式,使你能够复制已有对象,而又无需使代码依赖它们所属的类

别名

克隆(Clone)。

前言

1. 问题

如果你有一个对象,并希望生成与其完全相同的一个复制品,你该如何实现呢?首先,你必须新建一个属于相同类的对象。然后,你必须遍历原始对象的所有成员变量,并将成员变量值复制到新对象中

不错!但有个小问题。并非所有对象都能通过这种方式进行复制,因为有些对象可能拥有私有成员变量,它们在对象本身以外是不可见的

直接复制还有另外一个问题。因为你必须知道对象所属的类才能创建复制品,所以代码必须依赖该类。即使你可以接受额外的依赖性,那还有另外一个问题:有时你只知道对象所实现的接口,而不知道其所属的具体类,比如可向方法的某个参数传入实现了某个接口的任何对象。

2. 解决方案

原型模式将克隆过程委派给被克隆的实际对象。模式为所有支持克隆的对象声明了一个通用接口,该接口让你能够克隆对象, 同时又无需将代码和对象所属类耦合。通常情况下,这样的接口中仅包含一个“克隆”方法。

所有的类对“克隆”方法的实现都非常相似。该方法会创建一个当前类的对象,然后将原始对象所有的成员变量值复制到新建的类中。你甚至可以复制私有成员变量,因为绝大部分编程语言都允许对象访问其同类对象的私有成员变量。

支持克隆的对象即为原型。当你的对象有几十个成员变量和几百种类型时,对其进行克隆甚至可以代替子类的构造。

其运作方式如下:创建一系列不同类型的对象并不同的方式对其进行配置。如果所需对象与预先配置的对象相同,那么你只需克隆原型即可,无需新建一个对象。

结构

1. 基本实现

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

 2. 原型注册表实现

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

适用场景

适用场景

  • 如果你需要复制一些对象,同时又希望代码独立于这些对象所属的具体类,可以使用原型模式。

这一点考量通常出现在代码需要处理第三方代码通过接口传递过来的对象时。即使不考虑代码耦合的情况,你的代码也不能依赖这些对象所属的具体类,因为你不知道它们的具体信息。原型模式为客户端代码提供一个通用接口,客户端代码可通过这一接口与所有实现了克隆的对象进行交互,它也使得客户端代码与其所克隆的对象具体类独立开来。

  • 如果子类的区别仅在于其对象的初始化方式,那么你可以使用该模式来减少子类的数量。别人创建这些子类的目的可能是为了创建特定类型的对象。

在原型模式中,你可以使用一系列预生成的、各种类型的对象作为原型。客户端不必根据需求对子类进行实例化,只需找到合适的原型并对其进行克隆即可。

实现方式

  1. 创建原型接口,并在其中声明“克隆”方法。如果你已有类层次结构,则只需在其所有类中添加该方法即可。
  2. 原型类必须另行定义一个以该类对象为参数的构造函数。构造函数必须复制参数对象中的所有成员变量值到新建实体中。如果你需要修改子类,则必须调用父类构造函数,让父类复制其私有成员变量值。如果编程语言不支持方法重载,那么你可能需要定义一个特殊方法来复制对象数据。在构造函数中进行此类处理比较方便,因为它在调用new 运算符后会马上返回结果对象。
  3. 克隆方法通常只有一行代码: 使用new 运算符调用原型版本的构造函数。注意,每个类都必须显式重写克隆方法并使用自身类名调用new 运算符。否则, 克隆方法可能会生成父类的对象。
  4. 你还可以创建一个中心化原型注册表,用于存储常用原型。你可以新建一个工厂类来实现注册表,或者在原型基类中添加一个获取原型的静态方法。该方法必须能够根据客户端代码设定的条件进行搜索。搜索条件可以是简单的字符串,或者是一组复杂的搜索参数。找到合适的原型后,注册表应对原型进行克隆,并将复制生成的对象返回给客户端。最后还要将对子类构造函数的直接调用替换为对原型注册表工厂方法的调用。

优点

  • 你可以克隆对象,而无需与它们所属的具体类相耦合。
  • 你可以克隆预生成原型,避免反复运行初始化代码。
  • 你可以更方便地生成复杂对象。
  • 你可以用继承以外的方式来处理复杂对象的不同配置。

缺点

克隆包含循环引用的复杂对象可能会非常麻烦。

实例

Prototype.h

#ifndef PROTOTYPE_H_
#define PROTOTYPE_H_

// 抽象原型类
class Object {
 public:
    virtual Object* clone() = 0;
};

#endif  // PROTOTYPE_H_

 ConcretePrototype.h:

#ifndef CONCRETE_PROTOTYPE_H_
#define CONCRETE_PROTOTYPE_H_

#include <iostream>
#include <string>
#include "Prototype.h"


// 邮件的附件
class Attachment {
 public:
    void set_content(std::string content) {
        content_ = content;
    }
    std::string get_content() {
        return content_;
    }

 private:
    std::string content_;
};

// 具体原型: 邮件类
class Email : public Object {
 public:
    Email() {}
    Email(std::string text, std::string attachment_content) : text_(text), attachment_(new Attachment()) {
        attachment_->set_content(attachment_content);
    }
    ~Email() {
        if (attachment_ != nullptr) {
            delete attachment_;
            attachment_ = nullptr;
        }
    }

    void display() {
        std::cout << "------------查看邮件------------" << std::endl;
        std::cout << "正文: " << text_ << std::endl;
        std::cout << "邮件: " << attachment_->get_content() << std::endl;
        std::cout << "------------查看完毕------------" << std::endl;
    }

    // 深拷贝
    Email* clone() override {
        return new Email(this->text_, this->attachment_->get_content());
    }

    void changeText(std::string new_text) {
        text_ = new_text;
    }

    void changeAttachment(std::string content) {
        attachment_->set_content(content);
    }

 private:
    std::string text_;
    Attachment *attachment_ = nullptr;
};

#endif  // CONCRETE_PROTOTYPE_H_

 main.cpp:

#include "ConcretePrototype.h"

#include <cstdio>

int main() {
    Email* email = new Email("最初的文案", "最初的附件");
    Email* copy_email = email->clone();
    copy_email->changeText("新文案");
    copy_email->changeAttachment("新附件");
    std::cout << "original email:" << std::endl;
    email->display();
    std::cout << "copy email:" << std::endl;
    copy_email->display();

    delete email;
    delete copy_email;
}

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

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

相关文章

基于工业智能网关的设备运维管理平台有何功能?

工业物联网平台作为监控工业设备和工业环境的智能应用&#xff0c;整合边缘和云端的数据优势&#xff0c;在制造业领域得到越来越丰富的应用。 在工业制造生产过程中&#xff0c;常常分为人、机、料、法、环等五大要素&#xff0c;其中机器设备的安全稳定运行时保证工厂生产效…

58同城AI Lab在WeNet中开源GPU热词增强功能

01 前言 端到端语音识别系统在足够多数据上训练后&#xff0c;往往能达到不错的识别效果&#xff0c;然而在实际应用场景中&#xff0c;对于不常见的专有名词&#xff0c;例如人名、产品名、小区名等&#xff0c;往往容易识别错误&#xff0c;此类问题需要快速修复&#xff0c…

DNS是什么?DNS的工作流程

79. DNS是什么&#xff1f; DNS&#xff08;Domain Name System&#xff09;是一种用于将域名解析为相应IP地址的分布式命名系统&#xff0c;了解DNS对于理解域名解析原理和优化网络请求非常重要。本篇文章将介绍DNS的概念、工作原理以及在前端开发中的应用&#xff0c;帮助前…

正则表达式-捕获组,命名捕获组,非捕获组

正则表达式的作用 测试目标字符串是否符合规则 返回true/false按照规则从目标字符串提取内容 返回匹配的数组 在线测试工具 regex101: build, test, and debug regexRegular expression tester with syntax highlighting, explanation, cheat sheet for PHP/PCRE, Python, …

java适配达梦数据库

目录 一、数据库安装 二、数据库可视化工具 三、数据迁移 四、工程适配 新增maven依赖 配置文件修改 基于flyway的数据库版本管理 五、注意事项 一、数据库安装 官方文档&#xff1a;安装前准备 | 达梦技术文档 这里有一个点需要注意&#xff0c;如果你之前的数据库或…

【Java高级语法】(十六)方法引用:掌握Java中的方法引用,简化代码的实用指南~

Java高级语法详解之方法引用 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法形式3.2 案例 4️⃣ 应用场景5️⃣ 注意事项&#x1f33e; 总结 1️⃣ 概念 方法引用是Java编程语言中的一个重要特性&#xff0c;它首次出现在Java 8版本中。这一特性旨在简化函数式编程中使用Lambd…

【博客675】prometheus生产上易犯的错误

prometheus生产上易犯的错误 Mistake 1: Cardinality bombs 这是每个人在开始使用 Prometheus 时至少会遇到一次的经典陷阱。一旦您发现 Prometheus 基于标签的数据模型的有用性&#xff0c;您可能会想按各种有用的标签维度来拆分指标&#xff0c;直到您创建的时间序列超出 P…

【QT】枚举用到的宏详解:Q_ENUM,Q_FLAG,Q_DECLARE_FLAGS,Q_DECLARE_OPERATORS_FOR_FLAGS

目录 1. Q_ENUM宏 与 QMetaEnum类1.1 Q_ENUM宏的作用1.2 使用Q_ENUM注意的问题1.3 在写有关枚举的代码时&#xff0c;我们可能遇到这种情况&#xff1a;需要用到枚举的字符串&#xff0c;该怎么办&#xff1f;1.4 下面通过一段简单的代码来说明Q_ENUM的作用 2. Q_FLAG宏2.1 Q_F…

【SpringMVC】| 拦截器(含源码分析)

目录 拦截器 1. 拦截器的介绍 2. 拦截器的三个抽象方法 3. 拦截器的使用 4. 多个拦截器的执行顺序 Java核心技术大会 文末福利&#xff08;Java核心技术卷&#xff09; 拦截器 拦截器能拦截请求&#xff0c;前面学习的过滤器也能拦截请求&#xff0c;那两者有什么区别…

【数据结构与算法C++实现】1、异或的用法

原视频为左程云的B站教学 文章目录 1 异或换值2 求出数组中唯一一个出现奇数次的数3 求出数组中的两个出现奇数次的数 异或&#xff1a; 相同为0&#xff0c;不同为1。 更好的记忆方式&#xff1a; 不进位相加 10010 ^ 01100--------11110性质 0 ^ N N&#xff0c;N ^ N 0…

广电用户画像分析之根据用户行为数据进行筛选与标签添加

在数据处理和分析领域&#xff0c;我们经常需要根据用户的行为数据进行筛选和标签添加&#xff0c;以便更好地理解用户行为和偏好。在本篇博客中&#xff0c;我们将介绍两个示例&#xff0c;展示如何根据用户的收视行为数据和订单信息进行数据处理和分析。 前情提要&#xff1…

创新型影像测量仪器有哪些

走新型工业化之路&#xff0c;加快重塑竞争新优势&#xff0c;离不开更强的创新能力、更高的创新效率。新型工业化道路的基本标志和落脚点是要做到“科技含量高、经济效益好、资源消耗低、环境污染少、人力资源优势得到充分发挥”&#xff0c;并实现这几方面的兼顾和统一。而不…

spring boot 项目实现打包依赖分离

spring boot version 2.7 &#xff08;理论上是通用的&#xff09;Maven version 3 打包结果 重要文件以及文件夹解释 lib: 存在当前项目的全部依赖 other&#xff1a;和当前项目的 groupID 不同的依赖 project&#xff1a;和当前项目groupID 相同的依赖 XX-3.0.0-SNAPSHOT.j…

jdk安装及配置

一、下载安装包&#xff1a; 阿里云盘分享 提取码&#xff1a;am66 双击该程序 点击下一步 稍作等待即可。 二、配置环境变量 再新建一个系统变量CLASSPATH .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar 找到Path变量&#xff0c;双击编辑 Path变量。点击新建&…

【深度学习】GPT-2

在GPT1问世不久&#xff0c;和GPT很相似的BERT横空出世&#xff0c;并且在各方面都超越GPT-1。OpenAI在《Language Models are Unsupervised Multitask Learners》中于2019年提出的GPT-2&#xff0c;全称为Generative Pre-Training 2.0。提出语言模型式无监督的多任务学习 &…

DDS 信号发生器实验

目录 DDS 信号发生器实验 1、DDS 简介 2、实验任务 3、程序设计 3.1、DDS 顶层模块代码 3.2、clk_wiz IP 核 3.3、ILA IP 核&#xff08;集成逻辑分析器&#xff1a;Integrated Logic Analyzer&#xff0c;ILA&#xff09; 3.4、各波形参考代码 3.4.1、正弦信号波形采…

身份识别与访问管理(IAM)工具

AD360 是一款企业 IAM 解决方案&#xff0c;可帮助管理身份、保护访问并确保合规性。它具有强大的功能&#xff0c;例如自动化身份生命周期管理、安全 SSO、自适应 MFA、基于审批的工作流、UBA 驱动的身份威胁防护和历史审计报告。AD360 直观的界面和强大的功能使其成为满足现代…

行业云统领2023十大技术趋势,新华三把脉数实融合演进路径

“每一年的科技突破与环境变局&#xff0c;有一定的随机性又有一定的必然性&#xff0c;导致人类社会永远处于动态塑形的过程。”中国工程院院士陈晓红在想像未来的技术变化时认为&#xff1a;“无论怎么变化&#xff0c;人类未来图景仍然源于社会生活与经济发展的真实需求”。…

了解MySQL配置文件:位置、结构和选项

目录 1 MySQL配置文件的位置2 MySQL配置文件的结构3 MySQL配置选项4 [mysqld]部分&#xff1a;5 [client]部分&#xff1a;6 MySQL配置文件的重要性7 总结 本文详细介绍了MySQL配置文件的位置、结构和常用选项。了解如何使用MySQL配置文件来管理和配置MySQL服务器的行为和属性。…

5. QT环境下使用OPenCV(基于TCP实现摄像头图像数据的多线程传输)

1. 说明 通常情况下对于图像数据的采集可以放在后端进行,采集到的图像数据如果有需要可以通过通信将数据传输到前端进行显示,这其中需要使用到TCP数据传输协议和QT下的多线程开发技术。QT当中主线程一般是界面层次的,在主线程中执行耗时较长的数据操作,会引起界面的卡顿,…