C#设计模式-原型模式(Prototype Pattern)
引言
在软件开发过程中,我们习惯使用new来创建对象。但是当我们创建一个实例的过程很昂贵或者很复杂,并且需要创建多个这样的类的实例时。如果仍然用new操作符去创建这样的类的实例,会导致内存中多分配一个一样的类实例对象,增加创建类的复杂度和消耗更多的内存空间。
如果采用简单工厂模式来创建这样的系统。随着产品类增加,子类数量不断增加,会增加额外系统复杂程度,为此我们不得不引入原型模式了。
概念
原型模式(Prototype Pattern)是一种创建型设计模式, 使你能够复制对象, 甚至是复杂对象, 而又无需使代码依赖它们所属的类。
通过复制一个已经存在的实例来创建一个新的实例,而且不需知道任何创建的细节。被复制的实例被称为原型,这个原型是可定制的。
所有的原型类都必须有一个通用的接口, 使得即使在对象所属的具体类未知的情况下也能复制对象。 原型对象可以生成自身的完整副本, 因为相同类的对象可以相互访问对方的私有成员变量。
结构图

原型模式下主要角色:
- 原型(Prototype):声明一个克隆自身的接口,该角色一般有抽象类(Prototype)、接口(ICloneable)两种实现方式。
- 具体原型类(ConcretePrototype):实现原型(抽象类或接口)的 Clone() 方法,它是可被复制的对象。
- 访问类(Client):使用具体原型类中的 Clone() 方法来复制新的对象。
实现
假如有一个测试用例模板,项目A正在使用,公司又引进一个项目B,项目B的测试用例模板自己重新写一套肯定非常麻烦,那么可以使用项目A的用例模板,拿来改改就可以使用了。省却了许多时间。
使用浅拷贝实现
浅拷贝:将原来对象中的所有字段逐个复制到一个新对象,如果字段是值类型,则简单地复制一个副本到新对象,改变新对象的值类型字段不会影响原对象;如果字段是引用类型,则复制的是引用,改变目标对象中引用类型字段的值将会影响原对象。例如, 如果一个对象有一个指向引用类型(如测试用例的名称)的字段, 并且我们对该对象做了一个浅复制, 那麽两个对象将引用同一个引用(即同一个测试用例名称)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Prototype
{
class Program
{
static void Main(string[] args)
{
TestCase projectALoginCase = new TestCase
{
Id = 1001,
ProjectName = "A项目",
CreatTime = new DateTime(2020, 11, 19),
};
projectALoginCase.SetTestCaseContent("登录测试", "高", "打开登录页面并且登录", "登录成功"); TestCase projectBLoginCase = (TestCase)projectALoginCase.Clone();
projectBLoginCase.ProjectName = "B项目"; projectALoginCase.Show();
projectBLoginCase.Show();
Console.Read();
}
} /// <summary>
/// 实现了 ICloneable 接口
/// </summary>
public class TestCase : ICloneable
{
public TestCase()
{
mTestCaseContent = new TestCaseContent();
} private int id;
private string projectName;
private DateTime creatTime;
private TestCaseContent mTestCaseContent; public int Id
{
get { return id; }
set { id = value; }
} public string ProjectName
{
get { return projectName; }
set { projectName = value; }
} public DateTime CreatTime
{
get { return creatTime; }
set { creatTime = value; }
} public void Show()
{
Console.WriteLine($"Id:\t{this.Id}");
Console.WriteLine($"ProjectName:\t{this.ProjectName}");
Console.WriteLine($"CreatTime:\t{this.CreatTime}");
if (this.TestCaseContent != null)
{
this.TestCaseContent.show();
}
Console.WriteLine("================================================="); } /// <summary>
/// 关联一个引用类型
/// </summary>
public TestCaseContent TestCaseContent
{
get { return mTestCaseContent; }
}
public void SetTestCaseContent(string Name, string Level, string Step, string ExpectedResults)
{
this.mTestCaseContent.Name = Name;
this.mTestCaseContent.Level = Level;
this.mTestCaseContent.Step = Step;
this.mTestCaseContent.ExpectedResults = ExpectedResults;
} public object Clone()
{
// 浅拷贝对象的方法
return this.MemberwiseClone();
}
} /// <summary>
/// 测试用例内容类
/// </summary>
public class TestCaseContent
{
public string Name { get; set; }
public string Level { get; set; }
public string Step { get; set; }
public string ExpectedResults { get; set; } public void show() {
Console.WriteLine($"Name:\t{this.Name}");
Console.WriteLine($"Level:\t{this.Level}");
Console.WriteLine($"Step:\t{this.Step}");
Console.WriteLine($"ExpectedResults:\t{this.ExpectedResults}");
}
}
}
运行后结果
Id: 1001
ProjectName: A项目
CreatTime: 11/19/2020 12:00:00 AM
Name: 登录测试
Level: 高
Step: 打开登录页面并且登录
ExpectedResults: 登录成功
=================================================
Id: 1001
ProjectName: B项目
CreatTime: 11/19/2020 12:00:00 AM
Name: 登录测试
Level: 高
Step: 打开登录页面并且登录
ExpectedResults: 登录成功
=================================================
如果我们将拷贝后的项目B的测试用例的值进行重新设置,如下代码:
static void Main(string[] args)
{
TestCase projectALoginCase = new TestCase
{
Id = 1001,
ProjectName = "A项目",
CreatTime = new DateTime(2020, 11, 19),
};
projectALoginCase.SetTestCaseContent("登录测试", "高", "打开登录页面并且登录", "登录成功"); TestCase projectBLoginCase = (TestCase)projectALoginCase.Clone();
projectBLoginCase.ProjectName = "B项目";
projectBLoginCase.SetTestCaseContent("B项目登录测试", "级别高", "打开登录页面并且登录", "登录成功"); projectALoginCase.Show();
projectBLoginCase.Show();
Console.Read();
}
再次运行结果如下:
Id: 1001
ProjectName: A项目
CreatTime: 11/19/2020 12:00:00 AM
Name: B项目登录测试
Level: 级别高
Step: 打开登录页面并且登录
ExpectedResults: 登录成功
=================================================
Id: 1001
ProjectName: B项目
CreatTime: 11/19/2020 12:00:00 AM
Name: B项目登录测试
Level: 级别高
Step: 打开登录页面并且登录
ExpectedResults: 登录成功
=================================================
可以看的,通过浅拷贝后实践复制的是引用,改变目标对象中引用类型字段的值将会影响原对象。对于上面的实例显然是不可取的。修改B项目的测试用例影响到了A项目,肯定是有问题的。
接下来介绍使用深拷贝进行实现。
使用深拷贝实现
深拷贝:与浅复制不同之处在于对引用类型的处理,深复制将新对象中引用类型字段指向复制过的新对象,改变新对象中引用的任何对象,不会影响到原来的对象中对应字段的内容。例如,如果一个对象有一个指向引用类型(如测试用例的名称)的字段,并且对该对象做了一个深复制的话,将创建一个新的对象(即新的测试用例名称)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Prototype
{
class Program
{
static void Main(string[] args)
{
TestCase projectALoginCase = new TestCase
{
Id = 1001,
ProjectName = "A项目",
CreatTime = new DateTime(2020, 11, 19),
};
projectALoginCase.SetTestCaseContent("登录测试", "高", "打开登录页面并且登录", "登录成功"); TestCase projectBLoginCase = (TestCase)projectALoginCase.Clone();
projectBLoginCase.ProjectName = "B项目";
projectBLoginCase.SetTestCaseContent("B项目登录测试", "级别高", "打开登录页面并且登录", "登录成功"); projectALoginCase.Show();
projectBLoginCase.Show();
Console.Read();
}
} /// <summary>
/// 实现了 ICloneable 接口
/// </summary>
public class TestCase : ICloneable
{
public TestCase()
{
mTestCaseContent = new TestCaseContent();
} /// <summary>
/// 使用私有构造函数对引用类型进行复制
/// </summary>
/// <param name="testCaseContent"></param>
private TestCase(TestCaseContent testCaseContent)
{
this.mTestCaseContent = (TestCaseContent)testCaseContent.Clone();
} private int id;
private string projectName;
private DateTime creatTime;
private TestCaseContent mTestCaseContent; public int Id
{
get { return id; }
set { id = value; }
} public string ProjectName
{
get { return projectName; }
set { projectName = value; }
} public DateTime CreatTime
{
get { return creatTime; }
set { creatTime = value; }
} public void Show()
{
Console.WriteLine($"Id:\t{this.Id}");
Console.WriteLine($"ProjectName:\t{this.ProjectName}");
Console.WriteLine($"CreatTime:\t{this.CreatTime}");
if (this.mTestCaseContent != null)
{
this.mTestCaseContent.show();
}
Console.WriteLine("================================================="); } /// <summary>
/// 设置测试用例详细内容
/// </summary>
/// <param name="Name"></param>
/// <param name="Level"></param>
/// <param name="Step"></param>
/// <param name="ExpectedResults"></param>
public void SetTestCaseContent(string Name, string Level, string Step, string ExpectedResults)
{
this.mTestCaseContent.Name = Name;
this.mTestCaseContent.Level = Level;
this.mTestCaseContent.Step = Step;
this.mTestCaseContent.ExpectedResults = ExpectedResults;
} public object Clone()
{
// 创建一个全新的测试用例内容
TestCase newTestCase = new TestCase(this.mTestCaseContent); newTestCase.Id = this.Id;
newTestCase.ProjectName = this.ProjectName;
newTestCase.CreatTime = this.CreatTime; return newTestCase;
}
} /// <summary>
/// 测试用例内容类
/// </summary>
public class TestCaseContent:ICloneable
{
public string Name { get; set; }
public string Level { get; set; }
public string Step { get; set; }
public string ExpectedResults { get; set; } public object Clone()
{
// 浅拷贝
return this.MemberwiseClone();
} public void show() {
Console.WriteLine($"Name:\t{this.Name}");
Console.WriteLine($"Level:\t{this.Level}");
Console.WriteLine($"Step:\t{this.Step}");
Console.WriteLine($"ExpectedResults:\t{this.ExpectedResults}");
}
}
}
运行后结果:
Id: 1001
ProjectName: A项目
CreatTime: 11/19/2020 12:00:00 AM
Name: 登录测试
Level: 高
Step: 打开登录页面并且登录
ExpectedResults: 登录成功
=================================================
Id: 1001
ProjectName: B项目
CreatTime: 11/19/2020 12:00:00 AM
Name: B项目登录测试
Level: 级别高
Step: 打开登录页面并且登录
ExpectedResults: 登录成功
=================================================
从结果中可以看出,通过拷贝后A项目的测试用例还是A项目的,B项目的测试用例是B项目的。创建非常方便。
应用场景
原型模式通常适用于以下场景:
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
- 在实际项目中,原型模式很少单独出现,一般是和工厂模式一起出现,通过Clone方法创建一个对象,然后由工厂方法提供给调用者。
优缺点
优点:
- 原型模式向客户隐藏了创建新实例的复杂性
- 原型模式允许动态增加或较少产品类。
- 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
- 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
缺点:
- 每个类必须配备一个克隆方法。
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
C#设计模式-原型模式(Prototype Pattern)的更多相关文章
- C#设计模式——原型模式(Prototype Pattern)
一.概述 在软件开发中,经常会碰上某些对象,其创建的过程比较复杂,而且随着需求的变化,其创建过程也会发生剧烈的变化,但他们的接口却能比较稳定.对这类对象的创建,我们应该遵循依赖倒置原则,即抽象不应该依 ...
- 设计模式——原型模式(Prototype Pattern)
原型模式:用原型实例制定创建对象的种类,并且通过拷贝这些原型创建新的对象. UML 图: 原型类: package com.cnblog.clarck; /** * 原型类 * * @author c ...
- Net设计模式实例之原型模式( Prototype Pattern)
一.原型模式简介(Brief Introduction) 原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象. Specify the kin ...
- 乐在其中设计模式(C#) - 原型模式(Prototype Pattern)
原文:乐在其中设计模式(C#) - 原型模式(Prototype Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 原型模式(Prototype Pattern) 作者:weba ...
- 设计模式系列之原型模式(Prototype Pattern)——对象的克隆
说明:设计模式系列文章是读刘伟所著<设计模式的艺术之道(软件开发人员内功修炼之道)>一书的阅读笔记.个人感觉这本书讲的不错,有兴趣推荐读一读.详细内容也可以看看此书作者的博客https:/ ...
- 二十四种设计模式:原型模式(Prototype Pattern)
原型模式(Prototype Pattern) 介绍用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象.示例有一个Message实体类,现在要克隆它. MessageModel usin ...
- PHP设计模式 原型模式(Prototype)
定义 和工厂模式类似,用来创建对象.但实现机制不同,原型模式是先创建一个对象,采用clone的方式进行新对象的创建. 场景 大对象的创建. 优点 1.可以在运行时刻增加和删除产品 2.可以改变值或结构 ...
- 2.6 《硬啃设计模式》第8章 复制不是很难 - 原型模式(Prototype Pattern)
案例: 某即时战略游戏,你训练出来各种很强的战士. 为了增加游戏的可玩性,增加了一种复制魔法.实施该魔法,可以复制任意的战士. 你会怎样考虑这个设计? 在继续阅读之前,请先认真思考并写出你的设计,这样 ...
- python 设计模式之原型模式 Prototype Pattern
#引入 例子1: 孙悟空拔下一嘬猴毛,轻轻一吹就会变出好多的孙悟空来. 例子2:寄个快递下面是一个邮寄快递的场景:“给我寄个快递.”顾客说.“寄往什么地方?寄给……?”你问.“和上次差不多一样,只是邮 ...
- 【UE4 设计模式】原型模式 Prototype Pattern
概述 描述 使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.如孙悟空猴毛分身.鸣人影之分身.剑光分化.无限剑制 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象, ...
随机推荐
- kafka-伪集群搭建
一.简介 Apache Kafka是一个快速.可扩展的.高吞吐的.可容错的分布式"发布-订阅"消息系统,使用Scala与Java语言编写,能够将消息从一个端点传递到另一个端点, ...
- centos8上安装openresty
一,openresty的官网地址: http://openresty.org/ 说明:说一下openresty的安装方式: 从openresty的安装目录下,可以看到openresty编译安装了自己作 ...
- allure测试报告
首先如果你没有安装 pytest 库的话,先使用 pip 安装一下: pip install pytest 另外还需要安装 pytest 支持 allure 报告的插件库: pip install a ...
- SQL SERVER调优常用方法 sql优化
说起SQL SERVER的调优,我想大伙也很想知道这方面的知识.本人也正在探索的路上,大家有什么好的意见,欢迎一起探讨.研究.博取众人之长,才能扬长避短.本文中的内容主要是摘自<程序员的SQL金 ...
- matlab cvx工具箱解决线性优化问题
题目来源:数学建模算法与应用第二版(司守奎)第一章习题1.4 题目说明 作者在答案中已经说明,求解上述线性规划模型时,尽量用Lingo软件,如果使用Matlab软件求解,需要做变量替换,把二维决策变量 ...
- 微信小程序-基于高德地图API实现天气组件(动态效果)
微信小程序-基于高德地图API实现天气组件(动态效果) 在社区翻腾了许久,没有找到合适的天气插件.迫不得已,只好借鉴互联网上的web项目,手动迁移到小程序中使用.现在分享到互联网社区中,帮助后续有 ...
- 在Linux上成功启动Jenkins却无法访问的问题
本鸟最近打算学习Jenkins,正准备在Linux上面鼓捣一番,,却没想被入门级别问题当头一棒 下载完jenkins.war,使用java -jar命令在8088端口开启服务:java -jar je ...
- 助力全球抗疫:3D突发公共卫生事件管理平台
前言 秋冬降临,北半球气温转凉.欧洲多个国家单日新增病例持续创新高,美国更是成为全球疫情最严重的国家.国内山东青岛.新疆喀什等地也相继发现多例病情.全球第二波疫情已经开始,国内疫情牵动人心,全球抗疫仍 ...
- 签到功能,用 MySQL 还是 Redis ?
现在的网站和app开发中,签到是一个很常见的功能,如微博签到送积分,签到排行榜. 如移动app ,签到送流量等活动. 用户签到是提高用户粘性的有效手段,用的好能事半功倍! 下面我们从技术方面看看常 ...
- Luogu P3602 Koishi Loves Segments
传送门 题解 既然是选取区间,没说顺序 肯定先排遍序 都是套路 那么按什么排序呢??? 为了方便处理 我们把区间按左端点从小到大排序 把关键点也按从小到大排序 假设当扫到 \(i\) 点时,i 点之前 ...