设计模式系列-原型模式

news2025/1/19 17:16:47

一、上篇回顾

上篇创建者模式中,我们主要讲述了创建者的几类实现方案,和创建者模式的应用的场景和特点,创建者模式适合创建复杂的对象,并且这些对象的每

个组成部分的详细创建步骤可以是动态的变化的,但是每个对象的组装的过程来说可能是相对固定的或者说是对象的创建的过程是固定的,那么通过创建者

模式可以很好的解决这类复杂对象的创建,而在我们的生活中或者是项目中可能会有这个方面的需求,那么使用创建者模式无疑是好的选择。

创建者模式中的每个对象组成部分的构建都是对象本身提供的内部方法,具体的创建者只是调用要创建的对象的内部的相应组成部分的构建方法,组

织这些对象内部构建方法的执行顺序,完成对象的完整构建。当我们的客户应用程序需要调用这个创建者时,我们只需要通过指导者调用的形式,提供统一

的创建者访问入口,通过构造函数注入或者配置文件的形式来完成创建者的注入。

二、摘要

本文主要是讲述创建型模式中一个比较特殊的模式-原型模式,这个模式呢,有个最大的特点是克隆一个现有的对象,这个克隆的结果有2种,一种是

是浅复制,另一种是深复制,这里我们也会探讨下深复制和浅复制的原理,这样可能更方便大家理解这个原型模式的使用。我们都知道,创建型模式一般是

用来创建一个新的对象,然后我们使用这个对象完成一些对象的操作,我们通过原型模式可以快速的创建一个对象而不需要提供专门的new()操作就可以快

速完成对象的创建,这无疑是一种非常有效的方式,快速的创建一个新的对象。本文将会从以下几个方面进行讲述:

1、原型模式的使用场景和特点

2、浅复制和深复制的原理。

3、举例说明浅复制和深复制。

4、原型模式的实现方案。

5、总结原型模式。

我们这里先给出一个原型模式的原理图:

image

三、本文大纲

a、上篇回顾。

b、摘要。

c、本文大纲。

d、原型模式的特点及使用场景。

e、深复制和浅复制。

f、原型模式的实现方案。

g、原型模式使用总结。

h、系列进度。

i、下篇预告。

四、原型模式的特点及使用场景

原型模式的主要思想是基于现有的对象克隆一个新的对象出来,一般是有对象的内部提供克隆的方法,通过该方法返回一个对象的副本,这种创建对

象的方式,相比我们之前说的几类创建型模式还是有区别的,之前的讲述的工厂模式与抽象工厂都是通过工厂封装具体的new操作的过程,返回一个新的对

象,有的时候我们通过这样的创建工厂创建对象不值得,特别是以下的几个场景的时候,可能使用原型模式更简单也效率更高。

1、如果说我们的对象类型不是刚开始就能确定,而是这个类型是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的类型更容易。这个

怎么理解。例如我们有的时候在处理DataTable中的记录进行筛选后,放在一个新的DataTable 中,我们知道如果说2个dataTable的架构不同,那么必须

手动的显示的赋值,否则无法使用如下方式进行导入数据:

下面给出测试的相关代码和说明

public class DataTableDemo
{
public void CloneTest()
{
string cmdText = “SELECT * FROM TABLE”;
DataTable dt = new DataTable();
//通过执行上面的cmdText 返回一个dataTable对象;

//这时候我们可以如下形式复制一个新的dataTable,而不用先创建一个dataTable,然后把每一列都显示的循环添加到新的dataTable中,

//这是很大的工作量。
DataTable dt1 = dt.Clone();
//克隆一个新的对象 dt1.

#region 不采用克隆的形式复制一个新的dataTable

DataTable dt2 = new DataTable();

foreach (DataColumn column in dt.Columns)
{
dt2.Columns.Add(column.ColumnName);
}

#endregion
}
}

2、有的时候我们可能在实际的项目中需要一个对象在某个状态下的副本,这个前提很重要,这点怎么理解呢,例如有的时候我们需要对比一个对象经

过处理后的状态和处理前的状态是否发生过改变,可能我们就需要在执行某段处理之前,克隆这个对象此时状态的副本,然后等执行后的状态进行相应的对

比,这样的应用在项目中也是经常会出现的。

假设我们有这样的需求,我们在ORM框架的设计中,经常会遇到这样的问题,我们在处理某个对象的编辑状态的时候,我们想框架给我们生成的更新

数据库的SQL语句,不包含数据列没有发生变化的列,不要出现在更新语句中,这个时候,可能一个方案会是,编辑前克隆一个对象,然后等编辑后提交

的时候,生成相应的语句时进行对比之前克隆的对象,看看是否数据发生变化,如果说对象的部分数据列发生变化,那么就只是把变化的数据列进行更新。

当然上面我只是给出了一种比较简单的,但是效率不是很高的实现方案,还有很多好的方案我就不讨论了,这里只是为了说明原型模式的可用场景。

如果对上面的方式不是很理解或者看文字比较累的话,可以看下面的图,应该就比较清晰了。

image 这就是这种情况下可能原型模式有比较好的表现。

3、当我们在处理一些对象比较简单,并且对象之间的区别很小,可能只是很固定的几个属性不同的时候,可能我们使用原型模式更合适,例如我们生

活中的彩虹的七彩的颜色,等等,我们只需要根据现有的一个颜色对象,克隆一个新的颜色对象,然后修改具体的颜色的值就可以满足要求,然后如果通过

我们之前讲述的创建型工厂,抽象工厂模式等相对来说就引入新的依赖,并且复杂度也有所提高。例如我们的生活中的颜色的克隆:

image 我们都可以通过红色来克隆其他的所有颜色,只是修改相应的个别属性即可,远比创建一个新的对

象,然后给对象的各个属性赋值来的简单和方便,当然有的时候,如果我们并不需要基于现有的对象复制新的对象,或者我们需要的就是一个干净的空对

象,那么我的首先还是工厂模式或者抽象工厂模式啦。

五、深复制和浅复制

既然我们本篇讲述了原型模式的具体应用,那么我们就必须先搞清楚深复制和浅复制,否则也没有办清楚原型模式中的具体的克隆过程和克隆出来的

对象的详细情况。

.NET Freamwork 内置的每个继承自System.Object都有保护成员方法:

//
// 摘要:
// 创建当前 System.Object 的浅表副本。
//
// 返回结果:
// 当前 System.Object 的浅表副本。
[SecuritySafeCritical]
protected object MemberwiseClone();

系统为我们内置提供了复制对象本身的方法,不过这个方法返回的是一个浅复制的对象副本,而且.NET给我提供了一个System.ICloneable的接口,

我们通过实现这个接口,可以为对象提供自定义的克隆方法。

为了搞明白浅复制和深复制,那么我先要搞懂这2者的区别,.NET本身提供了浅复制的方法,而深复制的方法需要自己实现接口来完成。

我们先来看看浅复制后的对象和对象副本的情况:

image

我们再看看看深复制的对象和对象副本的情况:

image 通过上面的描述,大家应该针对浅复制和深复制的区别有了大概的了解,那么我们再结合程序代码来分

析吧,可能大家会更熟悉具体的应用。我们先来看看最简单的浅复制和深复制情况,我们这里举例来说吧:

我们定义一个杯子类,并且简单定义杯子的几项简单的属性,具体代码如下:

///
/// 杯子类
///
public class Cup : ICloneable
{
private double _rl;
private int _height;
private Factory _factory;
///
/// 高度
///
public int Height
{
get
{
return _height;
}
set
{
_height = value;
}
}

///
/// 容量
///
public double RL
{
get
{
return _rl;
}
set
{
_rl = value;
}
}

///
/// 生产厂家
///
public Factory Factory
{
get
{
return _factory;
}
set
{
_factory = value;
}
}

#region ICloneable 成员

public object Clone()
{
return this.MemberwiseClone();
}

#endregion
}

具体的测试代码:

class Program
{
static void Main(string[] args)
{
Cup cup = new Cup();
cup.Height = 2;
Cup cup1 = (Cup)cup.Clone();

cup1.Height = 1;
Console.WriteLine(cup.Height == cup1.Height);
System.Threading.Thread.Sleep(10000);
}
}

运行结果如下:

image

综上所述,我们知道,对于值类型的成员,浅复制也是在副本中重新创建的成员,对应到内存的栈上,分配新的内存空间。那么对于引用类型则因为浅复制

的时候,对象和对象副本共用同一个引用对象,那么不管是在对象还是对象副本中修改了相应的引用成员了之后,那么这个引用类型的成员就会发生变化。

因为2个对象指向同一个内存地址,那么任何一个修改操作都会产生改变。

那么对于上面的这个类如何修改这个类的实现才能实现深复制呢?

将上面的Clone方法如下实现:

public object Clone()
{
Cup cup = (Cup)this.MemberwiseClone();
Factory factory1 = new Factory();
factory1.FactoryName = this.Factory.FactoryName;
cup.Factory = factory1;

return cup;
}

这样就完成了对象的深复制,不管是值类型的成员还是引用类型的成员,这样的对象和对象副本,对任何一个成员属性的修改,都不会影响到改变对象的

值。

六、原型模式的实现方案

6.1 原型模式的经典实现

我们先来看看原型模式的经典实现,我们这里已颜色为例来说名下经典实现吧

定义一个接口, 用来表述所有的颜色对象接口:

public interface IColorDemo
{
IColorDemo Clone();

int Red
{
get;
set;
}
int Green
{
get;
set;
}
int Blue
{
get;
set;
}
}

我们这里给出红色的具体实现代码:

public class RedColor : IColorDemo
{
private int red;
private int green;
private int blue;
public int Red
{
get
{
return this.red;
}
set
{
this.red = value;
}
}
public int Green
{
get
{
return this.green;
}
set
{
this.green = value;
}
}
public int Blue
{
get
{
return this.blue;
}
set
{
this.blue = value;
}
}

#region IColorDemo 成员

public IColorDemo Clone()
{
return (IColorDemo)this.MemberwiseClone();
}

#endregion
}

因为上面的对于颜色,都是通过RGB不同的比例配置出来的,所以我就定义了3个整形的变量,所以我这里只是演示说明。那么具体的测试代码如下:

static void Main(string[] args)
{
IColorDemo color = new RedColor();
color.Red = 255;

IColorDemo color1 = color.Clone();
color1.Blue = 255;

Console.WriteLine(color.Blue == color1.Blue);
System.Threading.Thread.Sleep(10000);
}

返回的结果为false。代表对象副本的修改不会影响对象本身的状态。

6.2、原型模式的其他情况

上面讲述了简单的浅复制的情况,那么我们来分析下深复制原型的实现吧,深复制可能考虑的情况相对来说就会比较复杂,因为有可能对象是之间

有继承关系或者引用关系的时候,可能我们深复制的时候就需要注意,当然这对我们也是个考验。一般来说深复制一方面可以采用上面我给出的那种简单的

深复制对象的时候的方案,还可以通过序列化的形式来进行对象的复制。下面我们来通过序列化的形式来实现原型模式吧:

我们先给出序列化和反序列化的帮助类:

例如我们通过二进制的形式来进行序列化,我们都知道可以序列化的类必须打上标记,标识是否可以序列化,也可以在成员属性上定义。

///
/// 序列化和反序列化辅助类
///
public class SerializableHelper
{
public string Serializable(object target)
{
using (MemoryStream stream = new MemoryStream())
{
new BinaryFormatter().Serialize(stream, target);

return Convert.ToBase64String(stream.ToArray());
}
}

public object Derializable(string target)
{
byte[] targetArray = Convert.FromBase64String(target);

using (MemoryStream stream = new MemoryStream(targetArray))
{
return new BinaryFormatter().Deserialize(stream);
}
}

public T Derializable(string target)
{
return (T)Derializable(target);
}
}

下面给出简单的示例代码,还是使用上面的颜色对象为例。我们修改颜色类中的Clone方法

#region IColorDemo 成员

public IColorDemo Clone()
{
string target= SerializableHelper.Serializable(this);
return SerializableHelper.Derializable(target);
}

#endregion

程序的测试代码如下:

static void Main(string[] args)
{
IColorDemo color = new RedColor();
color.Red = 255;

IColorDemo color1 = color.Clone();
color1.Red = 234;

Console.WriteLine(color.Blue == color1.Blue);
System.Threading.Thread.Sleep(10000);
}

程序的运行结果为false,肯定二个对象是不同的,通过序列化和反序列化形成新的对象。其实只要是项目中要使用原型模式进行对象复制的情况

下,都可以通过序列化的形式来进行深复制。

七、原型模式使用总结

原型模式作为创建型模式中的最特殊的一个模式,具体的创建过程,是由对象本身提供,这样我们在很多的场景下,我们可以很方便的快速的构建新

的对象,就像前面分析讲解的几类场景中,可能我们通过使用对象的克隆,比通过其他几类的创建型模式,效果要好的多,而且代价也小很多。打个比方,

原型模式对于系统的扩展,可以做到无缝的扩展,为什么这么说呢?比如其他的创建型工厂,如果新增一个对象类型,那么我们不管是修改配置文件的方

式,还是修改代码的形式,无疑我们都是需要进行修改的,对于我们大家通用的公共应用来说这无疑是危险的,那么通过原型模式,则可以解决这样的问

题,因为类型本身实现这样的方法即可,但是也有一定的缺点,每个对象都实现这样的方法,无疑是很大的工作量,但是在某些特殊的环境下,或者实际的

项目中,可能原型模式是好的选择。

八、系列进度

创建型

1、系统架构技能之设计模式-单件模式

2、系统架构技能之设计模式-工厂模式

3、系统架构技能之设计模式-抽象工厂模式

4、系统架构技能之设计模式-创建者模式

5、系统架构技能之设计模式-原型模式

结构型

1、系统架构技能之设计模式-组合模式

2、系统架构技能之设计模式-外观模式

3、系统架构技能之设计模式-适配器模式

4、系统架构技能之设计模式-桥模式

5、系统架构技能之设计模式-装饰模式

6、系统架构技能之设计模式-享元模式

7、系统架构技能之设计模式-代理模式

行为型

1、系统架构技能之设计模式-命令模式

2、系统架构技能之设计模式-观察者模式

3、系统架构技能之设计模式-策略模式

4、系统架构技能之设计模式-职责模式

5、系统架构技能之设计模式-模板模式

6、系统架构技能之设计模式-中介者模式

7、系统架构技能之设计模式-解释器模式

九、下篇预告

下篇将会针对外观模式进行讲述,该模式也是结构型模式中很有特点设计模式之一,该 模式是将现有系统中的一些细粒度的东西通过外观对象包装起来,

在应用程序中访问这些方法的时候,通过外观类的形式,提供统一的访问入口,并且具体的细节,应用程序并不需要知道,这样就会降低程序调用的复杂

性,由于本人水平有限,不足或者有错误的地方,请大家批评指正,请大家继续支持我,谢谢。

十、Demo下载

下载本文Demo

转自:https://www.cnblogs.com/hegezhou_hot/archive/2010/12/04/1896471.html

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

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

相关文章

C语言入门 Day_12 一维数组

目录 前言 1.创建一维数组 2.使用一维数组 3.易错点 4.思维导图 前言 存储一个数据的时候我们可以使用变量, 比如这里我们定义一个记录语文考试分数的变量chinese_score,并给它赋值一个浮点数(float)。 float chinese_scoe…

服务器挂机赚钱之Traffmonetizer教程挂机,利用闲置的Vps赚钱

互联网VPS赚钱的项目traffmonetizer挂机,原理就是traffmonetizer通过共享闲置的带宽售卖给平台,然后平台将产生的收益就归你。如果你有闲置的vps服务器都可以通过traffmonetizer挂机赚钱。 VPS赚钱开始注册 实话实说吧,这个就是来拉人头的&…

使用 Python 和 dash 创建仪表板

推荐:使用 NSDT场景编辑器快速搭建3D应用场景 介绍 在数据科学和分析领域,数据的力量不仅通过提取见解来释放,而且还通过有效地传达这些见解来释放;这就是数据可视化发挥作用的地方。 数据可视化是信息和数据的图形表示。它使用图表、图形和…

CC1310开发工具下载及环境搭建

目录 CC1310开发工具集 CC1310SDK下载安装注意事项 CCS下载安装注意事项 导入示例代码 CCS常用设置 CC1310是TI的一款sub1G射频模块,具体参数见数据手册吧(CC1310数据手册、用户手册下载:https://dev.ti.com/tirex/explore/node?nodeA_…

2024年java面试--多线程(2)

系列文章目录 2024年java面试(一)–spring篇2024年java面试(二)–spring篇2024年java面试(三)–spring篇2024年java面试(四)–spring篇2024年java面试–集合篇2024年java面试–redi…

leetcode 189. 轮转数组

2023.9.3 k的取值范围为0~100000,此时需要考虑到两种情况,当k为0时,此时数组不需要轮转,因此直接return返回;当k大于等于数组nums的大小时,数组将会转为原来的数组,然后再接着轮转,此…

Linux系统下的zabbix监控平台(单机安装服务)

目录 一、zabbix的基本概述 二、zabbix构成 1.server 2.web页面 3.数据库 4.proxy 5.Agent 三、监控对象 四、zabbix的日常术语 1.主机(host) 2.主机组(host group) 3.监控项(item) 4.触发器(trigger) 5.事件(event) 6.动作(a…

docker笔记4:高级复杂安装-mysql主从复制

1.主从搭建步骤 1.1新建主服务器容器实例3307 docker run -p 3307:3306 --name mysql-master \ -v /mydata/mysql-master/log:/var/log/mysql \ -v /mydata/mysql-master/data:/var/lib/mysql \ -v /mydata/mysql-master/conf:/etc/mysql \ -e MYSQL_ROOT_PASSWORDroot \ -d…

java八股文面试[多线程]——虚假唤醒

阻塞队列中,如果需要线程挂起操作,判断有无数据的位置采用的是while循环 ,为什么不能换成if 肯定是不能换成if逻辑判断 线程A,线程B,线程E,线程C。 其中ABE生产者,C属于消费者 put阻塞代码&a…

UDP聊天室

1.头文件 /* * 文件名称&#xff1a;UDP.h * 创 建 者&#xff1a;crx * 创建日期&#xff1a;2023年09月3日 * 描 述&#xff1a; */ #ifndef _UDP_H #define _UDP_H#include <stdio.h> #include <sys/types.h> /* See NOTES */ #includ…

Day53|leetcode 1143.最长公共子序列、1035.不相交的线、53. 最大子序和

leetcode 1143.最长公共子序列 题目链接&#xff1a;1143. 最长公共子序列 - 力扣&#xff08;LeetCode&#xff09; 视频链接&#xff1a;动态规划子序列问题经典题目 | LeetCode&#xff1a;1143.最长公共子序列_哔哩哔哩_bilibili 题目概述 给定两个字符串 text1 和 text2&…

【Flutter】Flutter 使用 percent_indicator 实现基于百分比显示进度

【Flutter】Flutter 使用 percent_indicator 实现基于百分比显示进度 文章目录 一、前言二、安装和基本使用三、圆形百分比指示器四、线性百分比指示器五、完整示例六、总结 一、前言 今天我要为你介绍一个非常实用的Flutter包——percent_indicator。这个包允许我们基于百分比…

STM32f103入门(9)编码器接口测速

TIM3 PA6 PA7 上拉输入 原理上也是PWM捕获输入 捕获两个输入 我们用中断处理读取CNT的值 读取完将CNT置0 这样我们就得到了旋转编码器的速度/s 中断配置代码 #include "stm32f10x.h" // Device headervoid Timer_Init(void) {RCC_APB1PeriphClockC…

【Linux-Day9-进程间通信】

进程间通信 前提引入&#xff1a; 我们之前接触过管道 | 将前一个命令的输出结果作为后一个命令的输入 如&#xff1a;ps | grep “sleep” 其实就是将 ps 的结果写入到一个区域&#xff0c;在从这个区域读出目标数据 有名管道 mkfifo 创建管道文件 : mkfifo fifo ->创…

verilator——牛刀小试

verilator——牛刀小试 安装verilator可见&#xff1a;https://blog.csdn.net/qq_40676869/article/details/132648522?spm1001.2014.3001.5501 正文开始 编写一个异或的电路模块如下&#xff1a; top.v module top(input a,input b,output f );assign f a ^ b; endmodul…

[华为云云服务器评测] 华为云耀云服务器 Java、node环境配置

系列文章目录 第一章 [linux实战] 华为云耀云服务器L实例 Java、node环境配置 文章目录 系列文章目录前言一、任务拆解二、修改密码三、配置安全规则四、远程登录并更新apt五、安装、配置JDK环境5.1、安装openjdk,选择8版本5.2、检查jdk配置 六、安装、配置git6.1、安装git6.2…

【从0学习Solidity】合约入门 Hello Web3

【学习Solidity的基础】入门智能合约开发 Hello Web3 &#x1f4f1;不写代码没饭吃上架主页 在强者的眼中&#xff0c;没有最好&#xff0c;只有更好。我们是全栈开发领域的优质创作者&#xff0c;同时也是阿里云专家博主。 ✨ 关注我们的主页&#xff0c;探索全栈开发的无限…

torch.bmm功能解读

bmm 是 batch matrix multiple 的简写&#xff0c;即批量矩阵乘法&#xff0c;矩阵是二维的&#xff0c;加上batch一个维度&#xff0c;因此该函数的输入必须是两个三维的 tensor&#xff0c;三个维度代表的含义分别是&#xff1a;&#xff08;批量&#xff0c;行&#xff0c;列…

递归与递推

会独立敲一遍代码并debug&#xff08;1&#xff0c;3题较难&#xff1b;2&#xff0c;4题较简单&#xff09; 部分题需要买课&#xff0c;可到洛谷或其他OJ找原题 目录 &#x1f4d5;空间复杂度(计算方法) &#x1f33c;1&#xff0c;费解的开关 &#x1f33c;2&#xff0c;…

图神经网络教程之HAN-异构图模型

异构图 包含不同类型节点和链接的异构图 异构图的定义&#xff1a;节点类别数量和边的类别数量加起来大于2就叫异构图。 meta-path元路径的定义&#xff1a;连接两个对象的复合关系&#xff0c;比如&#xff0c;节点类型A和节点类型B&#xff0c;A-B-A和B-A-B都是一种元路径。 …