设计模式系列-原型模式

一、上篇回顾

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

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

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

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

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

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

二、摘要

本文主要是讲述创建型模式中一个比较特殊的模式-原型模式,这个模式呢,有个最大的特点是克隆一个现有的对象,这个克隆的结果有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.mzph.cn/news/74220.shtml

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

相关文章

uniapp的webview实现左滑返回上一个页面

uniapp默认左滑是关闭整个webview&#xff0c;而不是关闭当前页 实现思路&#xff1a;拦截webview的url跳转操作&#xff0c;将新url用webview组件重新打开&#xff0c;当左滑的时候&#xff0c;默认关闭的就是当前webview&#xff0c;继而跳转到上一次的页面中 <template&…

LeetCode 731. My Calendar II【设计,有序映射,差分;线段树】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

剑指 Offer 07. 重建二叉树

题目描述 输入某二叉树的前序遍历和中序遍历的结果&#xff0c;请构建该二叉树并返回其根节点。 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 解题思路 首先&#xff0c;根据前序遍历结果确定根节点。前序遍历的第一个元素即为根节点的值。 接下来&#xff0c…

第17节-PhotoShop基础课程-画笔修复工具

文章目录 前言1.画笔工具1.基本操作2.工具选项1.不透明度2.流量3.平滑2.画笔大小工具栏大小设置 4.笔刷 2.铅笔工具3.颜色替换工具 批量替换颜色4.混合器画笔工具-人像精修 前言 画笔工具的使用 1.画笔工具 1.基本操作 画画 2.工具选项 1.不透明度 2.流量 设置低了会有间隔&…

第29节-PhotoShop基础课程-滤镜库

文章目录 前言1.滤镜库2.Camera Raw滤镜 &#xff08;用来对图片进行预处理&#xff0c;最全面的一个&#xff09;3.神经滤镜&#xff08;2022插件 需要先下载&#xff09;4.液化&#xff08;胖-> 瘦 矮->高&#xff09;5.其它滤镜1.自适应广角2.镜头矫正 把图片放正3.消…

Kafka详解

目录 一、消息系统 1、点对点的消息系统 2、发布-订阅消息系统 二、Apache Kafka 简介 三、Apache Kafka基本原理 3.1 分布式和分区&#xff08;distributed、partitioned&#xff09; 3.2 副本&#xff08;replicated &#xff09; 3.3 整体数据流程 3.4 消息传送机制…

通过jr-qrcode生成二维码并下载到客户端本地(Vue)

生成二维码 首先生成二维码图片的地址 引入jr-qrcode import jrQrcode from jr-qrcode; 生成二维码图片的地址 // 生成二维码地址 getQRCodeUrl(spreadUrl) {const QRCodeUrl jrQrcode.getQrBase64(spreadUrl);return QRCodeUrl; }that.backUrl jrQrcode.getQrBase64(da…

JP《乡村振兴振兴战略下传统村落文化旅游设计》许少辉书香续,山水长

JP《乡村振兴振兴战略下传统村落文化旅游设计》许少辉书香续&#xff0c;山水长

Vue2+Vue3基础入门到实战项目(前接六 副线一)—— 面经 项目

day1 接口文档地址&#xff1a;https://www.apifox.cn/apidoc/project-934563/api-20384515 一、项目功能演示 1.目标 启动准备好的代码&#xff0c;演示移动端面经内容&#xff0c;明确功能模块 2.项目收获 二、项目创建目录初始化 vue-cli 建项目 1.安装脚手架 (已安装…

Python的sort()与sorted()函数详解

目录 sort&#xff08;&#xff09;函数 sorted&#xff08;&#xff09;函数 key参数 区别 sort&#xff08;&#xff09;函数 sort()方法&#xff1a;该方法用于原地对列表进行排序&#xff0c;即直接在原始列表上进行排序操作&#xff0c;并不返回一个新的列表。 my_l…

MySQL MHA

什么是 MHA MHA&#xff08;Master High Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件 MHA 的出现就是解决MySQL 单点故障的问题 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作 MHA能在故障切换的过程中最大程度上…

WebSocket的那些事(5-Spring STOMP支持之连接外部消息代理)

目录 一、序言二、开启RabbitMQ外部消息代理三、代码示例1、Maven依赖项2、相关实体3、自定义用户认证拦截器4、Websocket外部消息代理配置5、ChatController6、前端页面chat.html 四、测试示例1、群聊、私聊、后台定时推送测试2、登录RabbitMQ控制台查看队列信息 五、结语 一、…

第4章_瑞萨MCU零基础入门系列教程之瑞萨 MCU 源码设计规范

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

uniApp监听左右滑动事件

监听左右滑动事件的步骤 1. 添加需要监听滑动事件的元素 在你的页面中&#xff0c;添加需要监听滑动事件的元素。这可以是一个 view、swiper 或其他组件&#xff0c;取决于你的需求。例如&#xff1a; <template><view class"body" touchstart"touc…

deepin V23通过flathub安装steam畅玩游戏

deepin V23缺少32位库&#xff0c;在星火商店安装的steam,打开报错&#xff0c;无法使用&#xff01; 通过flathub网站安装steam,可以正常使用&#xff0c;详细教程如下&#xff1a; flathub网址&#xff1a;主页 | Flathub 注意&#xff1a;flathub下载速度慢&#xff0c;只…

Redis从基础到进阶篇(四)----性能调优、分布式锁与缓存问题

目录 一、Redis 集群演变 1.1 ReplicationSentinel*高可用 1.2 ProxyReplicationSentinel(仅仅了解) 1.3 Redis Cluster 集群 (重点&#xff09; 1.3.1 Redis-cluster架构图 1.3.2 工作原理 1.3.3 主从切换 1.3.4 副本漂移 1.3.5 分片漂移 二、Redis版本历史&#xf…

ODC现已开源:与开发者共创企业级的数据库协同开发工具

OceanBase 开发者中心&#xff08;OceanBase Developer Center&#xff0c;以下简称 ODC&#xff09;是一款开源的数据库开发和数据库管理协同工具&#xff0c;从首个版本上线距今已经发展了三年有余&#xff0c;ODC 逐步由一款专为 OceanBase 打造的开发者工具演进成为支持多数…

xCode14.3.1运行MonkeyDev出现“Executable Not Found“的解决办法

安装MonkeyDev遇到的坑 环境&#xff1a;Xcode Version 14.3.1 (14E300c) 错误提示 is not a valid path to an executable file. 报错 /Users/xxxx//Library/Developer/Xcode/DerivedData/MonTest-ccparhdyzjuqhjdergwrngpfwwoh/Build/Products/Debug-iphoneos/MonTest.app…

go-zerogo web集成redis实战

前言 上一篇&#xff1a;go-zero&go web集成JWT和cobra命令行工具实战 从零开始基于go-zero搭建go web项目实战-03集成redis实战 源码仓库地址 源码 https://gitee.com/li_zheng/treasure-box golang redis 客户端 Go-Redis 地址&#xff1a; GitHub: https://github.…

Maven 知识点总结

文章目录 Maven1、Maven 坐标2、Maven 仓库3、Maven 依赖依赖配置依赖范围依赖调解原则排除依赖 4、Maven 生命周期5、Maven 聚合与继承 Maven Maven是一个项目管理工具&#xff0c;它包含了项目对象模型&#xff08;POM&#xff1a;Project Object Model&#xff09;&#xf…