本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/7640873.html,记录一下学习过程以备后续查用。

 一、引言

很多人说原型设计模式会节省机器内存,他们说是拷贝出来的对象是原型的复制,不会使用内存。我认为这是不对的,因为拷贝出来的每一个对象都是实际

存在的,每个对象都有自己独立的内存地址且会被GC回收。如果就浅拷贝来说,可能会公用一些字段(引用类型),但深拷贝是不会的。所以说原型设计模式会

提高内存使用率是不一定的,具体还要看当时的设计,如果拷贝出来的对象缓存了,每次使用的是缓存的拷贝对象,那就另当别论,再说该模式本身解决的不

是内存使用率的问题。

附:浅复制与深复制的区别

浅复制一个对象:

1)如果这个对象(如int age=18 )是值类型,则得到的对象是一个全新的值类型对象(新的内存地址);

2)如果这个对象是引用类型(如class Person):

I、这个对象中的值类型(如person.Age=18)是一个全新的值类型对象(新的内存地址);

II、这个对象中的引用类型是公用的(同一内存地址),当原始引用类型的值变化时,新生成对象的引用类型的值也会跟着变化。

深复制一个对象:

无论之前这个对象是值类型还是引用类型,得到的新对象都是一个全新的对象(新的内存地址)。

在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样的类的实例时,如果用new操作符去创建时,会增加创建的复杂度

与客户代码的耦合度。如果采用工厂方法模式来创建这样的实例对象的话,随着产品类的不断增加,导致子类的数量不断增多,也导致了相应工厂类的增加,

系统复杂程度随之增加,所以此时使用工厂方法模式来封装类的创建过程并不合适。

由于每个类的实例都是相同的(这个相同指的是类型相同,但是每个实例的状态参数会有不同,如果状态数值也相同就没意义了),有一个这样的对象就可

以了。当我们需要多个相同的类实例时,可以通过对原来对象拷贝一份来完成创建,这个思路正是原型模式的实现方式。

二、原型模式介绍

原型模式:英文名称--Prototype Pattern;分类--创建型。

2.1、动机(Motivate)

在软件系统中,经常面临着“某些结构复杂的对象”的创建工作,由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。

如何应对这种变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?

2.2、意图(Intent)

使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。--《设计模式》Gof

2.3、结构图(Structure)

2.4、模式的组成

从上图可以看出,在原型模式的结构图有以下角色:

1)原型类(Prototype):原型类,声明一个Clone自身的接口。

2)具体原型类(ConcretePrototype):实现一个Clone自身的操作。

在原型模式中,Prototype通常提供一个包含Clone方法的接口,具体的原型ConcretePrototype使用Clone方法完成对象的创建。

2.5、原型模式的具体实现

《大话西游之大圣娶亲》这部电影,里面有这样一个场景:牛魔王使用无敌牛虱大战至尊宝,至尊宝的应对之策就是--从脑后拔下一撮猴毛,吹了口仙气,

无数猴子猴孙现身来大战牛魔王的无敌牛虱。至尊宝的猴子猴孙就是该原型模式的最好体现,至尊宝创建自己的一个副本,不用还要重新孕育五百年,然后出

世、再学艺,最后再来和老牛大战,假如这样的话,估计黄花菜都凉了。至尊宝有3根救命猴毛,轻轻一吹,想要多少个自己就有多少个,方便、快捷。

    class Program
{
/// <summary>
/// 抽象原型,定义了原型本身所具有特征和动作,该类型就是至尊宝。
/// </summary>
public abstract class Prototype
{
//战斗--保护师傅
public abstract void Fight();
//化缘--不要饿着师傅
public abstract void BegAlms(); //吹口仙气--变一个自己出来
public abstract Prototype Clone();
} /// <summary>
/// 具体原型,例如:行者孙A,他只负责与从天界宠物下界的妖怪战斗和化缘斋饭食。
/// </summary>
public sealed class MonkeyKingPrototype : Prototype
{
//战斗--保护师傅
public override void Fight()
{
Console.WriteLine("七十二变,集万千武艺于一身。");
}
//化缘--不要饿着师傅
public override void BegAlms()
{
Console.WriteLine("阿弥陀佛!施主,请施舍点饭食。");
} //吹口仙气--变一个自己出来
public override Prototype Clone()
{
return (MonkeyKingPrototype)MemberwiseClone();
}
} /// <summary>
/// 具体原型,例如:孙行者B,他只负责与自然界修炼成妖的妖怪战斗和化缘水果。
/// </summary>
public sealed class NewskyPrototype : Prototype
{
//战斗--保护师傅
public override void Fight()
{
Console.WriteLine("七十二变,集万千武艺于一身。");
}
//化缘--不要饿着师傅
public override void BegAlms()
{
Console.WriteLine("阿弥陀佛!施主,请施舍点水果。");
} //吹口仙气--变一个自己出来
public override Prototype Clone()
{
return (NewskyPrototype)MemberwiseClone();
}
} static void Main(string[] args)
{
#region 原型模式
Prototype monkeyKing = new MonkeyKingPrototype();
Prototype monkeyKing1 = monkeyKing.Clone();
Prototype monkeyKing2 = monkeyKing.Clone(); Prototype newsky = new NewskyPrototype();
Prototype newsky1 = newsky.Clone();
Prototype newsky2 = newsky.Clone(); //孙行者A打妖怪
monkeyKing1.Fight();
//孙行者B去化缘
newsky2.BegAlms(); Console.Read();
#endregion
}
}

运行结果如下:

三、原型模式的实现要点

Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有“稳定的接口”。

Prototype模式对于“如何创建易变类的实体对象”(创建型模式除了Singleton模式以外,都是用于解决创建易变类的实体对象的问题的)采用“原型克隆”的方

法来做,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象——所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方不断

地Clone。

Prototype模式中的Clone方法可以利用.NET中的Object类的MemberwiseClone()方法或者序列化来实现深拷贝。

3.1、原型模式的优点

1)原型模式向客户隐藏了创建新实例的复杂性。

2)原型模式允许动态增加或较少产品类。

3)原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。

4)产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构。

3.2、原型模式的缺点

1)每个类必须配备一个克隆方法。

2)配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或

者引用含有循环结构的时候。

3.3、原型模式的使用场景

1)资源优化场景

类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。

2)性能和安全要求的场景

通过new产生一个对象需要非常繁琐的数据准备或访问权限时,则可以使用原型模式。

3)一个对象多个修改者的场景

一个对象需要提供给其它对象访问而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。在实际项目中,原型模式很少

单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。

四、.NET中原型模式的实现

在.NET中,微软已经为我们提供了原型模式的接口实现,该接口就是ICloneable。其实这个接口就是抽象原型,提供克隆方法,相当于与上面代码中Prototype

抽象类,其中的Clone()方法实现原型模式。如果想自定义的类具有克隆的功能,首先需要在类定义时实现ICloneable接口的Clone方法。

namespace System
{
[ComVisible(true)]
public interface ICloneable
{
object Clone();
}
}

其实在.NET中实现了ICloneable接口的类有很多,如下图所示(只截取了部分,可以用ILSpy反编译工具进行查看):

五、总结

到本篇为止,所有的创建型设计模式就写完了。学习设计模式应该是一个循序渐进的过程,当我们写代码的时候不要一上来就用什么设计模式,而是通过重构

来使用设计模式。

下面总结一下创建型的设计模式:

单例模式解决的是实体对象个数的问题。除了单例模式之外,其它的创建型模式解决的都是new所带来的耦合关系。工厂方法模式、抽象工厂模式、建造者模

式都需要一个额外的工厂类来负责实例化“易变对象”,而原型模式则是通过原型(一个特殊的工厂类把工厂和实体对象耦合在一起了)来克隆“易变对象”。如果

遇到“易变类”,起初的设计通常从工厂方法模式开始,当遇到更多的复杂变化时,再考虑重构为其他三种工厂模式(抽象工厂模式、建造者模式、原型模式)。

一般来说,如果可以使用工厂方法模式,那么一定可以使用原型模式,但是原型模式的使用情况一般是在类比较容易克隆的条件之上。如果是每个类的实现都

比较简单,只需要实现MemberwiseClone而没有引用类型的深拷贝,那么就更加适合了。

C#设计模式学习笔记:(5)原型模式的更多相关文章

  1. 设计模式学习笔记——Prototype原型模式

    原型模型就是克隆. 还有深克隆.浅克隆,一切听上去都那么耳熟能详.

  2. 设计模式学习系列6 原型模式(prototype)

    原型模式(prototype)用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.允许一个对象再创建另外一个新对象的时候根本无需知道任何创建细节,只需要请求圆形对象的copy函数皆可. 1 ...

  3. 设计模式学习笔记--备忘录(Mamento)模式

    写在模式学习之前 什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方式,这就是软件模式:每个模式描写叙述了一个在我们程序设计中常常发生的问题,以及该问题的解决方式:当我们碰到模 ...

  4. java学习笔记之原型模式及深浅拷贝

    一.原型模式的基本介绍 在聊原型模式之前,我们来思考一个小问题,传统的方式我们是如何克隆对象呢? 那我们以多利羊(Sheep)为例,来说明上述这个问题,具体代码见下面: 多利羊(Sheep) publ ...

  5. 设计模式学习笔记-Adapter模式

    Adapter模式,就是适配器模式,使两个原本没有关联的类结合一起使用. 平时我们会经常碰到这样的情况,有了两个现成的类,它们之间没有什么联系,但是我们现在既想用其中一个类的方法,同时也想用另外一个类 ...

  6. Java-马士兵设计模式学习笔记-装饰者模式

    Java装饰者模式简介 一.假设有一个Worker接口,它有一个doSomething方法,Plumber和Carpenter都实现了Worker接口,代码及关系如下: 1.Worker.java p ...

  7. 研磨设计模式学习笔记2--外观模式Facade

    需求:客户端需要按照需求,执行一个操作,操作包括一个系统中的3个模块(根据配置选择是否全部执行). 外观模式优点: 客户端无需知道系统内部实现,,只需要写好配置文件,控制那些模块执行,简单易用. 外观 ...

  8. 设计模式学习笔记 1.factory 模式

    Factory 模式 用户不关心工厂的具体类型,只知道这是一个工厂就行. 通过工厂的实现推迟到子类里面去来确定工厂的具体类型. 工厂的具体类型来确定生产的具体产品. 同时用户不关心这是一个什么样子的产 ...

  9. 设计模式学习笔记——Composite 组合模式

    用于描述无限层级的复杂对象,类似于描述资源管理器,抽象出每一个层级的共同特点(文件夹和文件,展开事件) 以前描述一个对象,是将整个对象的全部数据都描述清楚,而组合模式通过在对象中定义自己,描述自己的下 ...

  10. 设计模式学习笔记——Bridge 桥接模式

    先说一下我以前对桥接模式的理解:当每个类中都使用到了同样的属性或方法时,应该将他们单独抽象出来,变成这些类的属性和方法(避免重复造轮子),当时的感觉是和三层模型中的model有点单相似,也就是让mod ...

随机推荐

  1. Docker(二) 镜像

    简介 Docker镜像是什么? 它是一个只读的文件,就类似于我们安装操作系统时候所需要的那个iso光盘镜像,通过运行这个镜像来完成各种应用的部署. 这里的镜像就是一个能被docker运行起来的一个程序 ...

  2. Spring 依赖注入原理

    所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中.当spring容器启动后,spring容器初始化,创建并管理bean对象,以及销毁它.所以我们只需从容器直接获取Bean对象就行, ...

  3. c/c++判断文件是否存在

    #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <fstream> #include <cstd ...

  4. CMake中的两种变量(Variable types in CMake)

    在CMake中存在两种变量:normal variables and cache varialbes .正常变量就像是脚本内部变量,相当于程序设计中定义的局部变量那样.而CMakeLists.txt相 ...

  5. AWS的边缘计算平台GreenGrass和IoT

    AWS的边缘计算平台GreenGrass和IoT 为什么需要有边缘计算? 如今公有云和私有云平台提供的服务已经连接上了绝大多数的桌面设备和移动设备.但是更多的设备比如,车辆,工程机械,医疗设备,无人机 ...

  6. python学习记录(七)

    0904--https://www.cnblogs.com/fnng/archive/2013/04/24/3039335.html 0904--https://www.cnblogs.com/fnn ...

  7. CCF_201612-1_最大波动

    http://115.28.138.223/view.page?gpid=T47 水. #include<iostream> #include<cstring> #includ ...

  8. 用Java实现简单的网络聊天程序

    Socket套接字定义: 套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开.读写和关闭等操作.套接字允许应用程序将I/O插入到网络中,并与网络中的其他 ...

  9. Codeforces 922 E Birds (背包dp)被define坑了的一题

    网页链接:点击打开链接 Apart from plush toys, Imp is a huge fan of little yellow birds! To summon birds, Imp ne ...

  10. 2020-02-20Linux学习日记,第二天

    在内容开始前请教一下博客园的大佬.编辑器中没有看到格式刷,要怎么不连续的选中内容给予想要的格式,有看到的麻烦私信解答一下,谢谢! ----------------------------------- ...