一、介绍

原型模式是一个创建型的模式。原型二字表明了该模型应该有一个样板实例,用户从这个样板对象中复制出一个内部属性一致的对象,这个过程也就是我们俗称的“克隆”。被复制的实例就是我们所称的“原型”,这个原型也是可定制的。原型模型多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效。

二、定义

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

三、使用场景

  • (1)类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。

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

  • (3)一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。

需要注意的是,通过实行Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能够获得效率上的提升。因此,在使用Cloneable时需要考虑构建对象的成本以及做一些效率上的测试。当然,实现原型模式也不一定非要实现Cloneable接口,也有其他的实现方式,这里将会对这些一一说明。

四、原型模型的UML类图

图中角色介绍:

  • Client:客户端用户。

  • Prototype:抽象类或者接口,声明具备clone能力。

  • ConcretePrototype:具体的原型类。

五、原型模式的简单实现

下面以简单的文档拷贝为例来演示一下简单的原型模式,我们在这个例子中首先创建了一个文档对象,即WordDocument,这个文档中含有文字和图片。用户经过了长时间的内容编辑后,打算对该文档做进一步的编辑,但是,这个编辑后的文档是否会被采用还不确定,因此,为了安全起见,用户需要将当前文档拷贝一份,然后再在文档副本上进行修改,这与《Effective Java》一书中提到的保护性拷贝有些类似,如此,这个原始文档就是我们上述所说的样板实例,也就是将要被“克隆”的对象,我们成为原型:

示例代码:

/*** 文档类型,扮演的是ConcretePrototype角色,而cloneable是代表prototype角色*/
public class WordDocument implements Cloneable {//文本private String mText;//图片名列表private ArrayList<String> mImages = new ArrayList<String>();public WordDocument(){System.out.println("-------- WordDocument构造函数 --------");}public String getText(){return this.mText;}public void setText(String text){this.mText = text;}public ArrayList<String> getImages(){return this.mImages;}public void setImages(ArrayList<String> images){this.mImages = images;}public void addImage(String img){this.mImages.add(img);}/*** 打印文档*/public void showDocument(){System.out.println("-------- Word Content Start --------");System.out.println("Text : " + this.mText);System.out.println("Images List : ");for(String image : mImages){System.out.println("image name : " + image);}System.out.println("-------- Word Content End --------");}@Overrideprotected WordDocument clone(){try{WordDocument doc = (WordDocument)super.clone();doc.mText = this.mText;doc.mImages = this.mImages;return doc;}catch(Exception e){}return null;}
}

执行方法:

    public static void main(String[] args) throws IOException {//1.构建文档对象WordDocument originDoc = new WordDocument();//2.编辑文档,添加图片等originDoc.setText("这是一篇文档");originDoc.addImage("图片一");originDoc.addImage("图片二");originDoc.addImage("图片三");originDoc.showDocument();//以原始文档为原型,拷贝一份副本WordDocument doc2 = originDoc.clone();doc2.showDocument();//修改文档副本doc2.setText("这是修改过的Doc2文本");doc2.addImage("这是新添加的图片");originDoc.showDocument();doc2.showDocument();}

执行结果:

-------- WordDocument构造函数 --------
//originDoc
-------- Word Content Start --------
Text : 这是一篇文档
Images List :
image name : 图片一
image name : 图片二
image name : 图片三
-------- Word Content End --------//doc2
-------- Word Content Start --------
Text : 这是一篇文档
Images List :
image name : 图片一
image name : 图片二
image name : 图片三
-------- Word Content End --------//副本修改后originDoc
-------- Word Content Start --------
Text : 这是一篇文档
Images List :
image name : 图片一
image name : 图片二
image name : 图片三
image name : 这是新添加的图片
-------- Word Content End --------//副本修改后doc2
-------- Word Content Start --------
Text : 这是修改过的Doc2文本
Images List :
image name : 图片一
image name : 图片二
image name : 图片三
image name : 这是新添加的图片
-------- Word Content End --------

这里我们发现通过修改doc2后,只是影响了originDoc的mImages,而没有改变mText。

六、浅拷贝和深拷贝

上述原型模式的实现实际上只是一个浅拷贝,也称影子拷贝,这份拷贝实际上并不是将原始的文档的所有字段都重新构造了一份,而是副本文档的字段引用原始文档的字段,如下图:

细心的读者可能从上面的结果中发现,最后两个文档信息输出是一致的。我们在doc2添加了一张图片,但是,同时也显示在originDoc中,这是怎么回事呢?学习过C++的读者都会有比较深刻的体会,这是因为上文中WordDocument的clone方法中只是简单的进行了浅拷贝,引用类型的新对象doc2.mImages只是单纯的指向了this.mImages引用,并没有重新构造一个mImages对象,然后将原始文档中的图片添加到新的mImages对象中,这样就导致doc2.mImages与原始文档中的是同一个对象,因此,修改了其中一个文档中的图片,另一个文档也会受影响。那么如何解决这个问题呢?答案就是采用深拷贝,即在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯引用的形式。

clone方法修改如下(其他不变):

    @Overrideprotected WordDocument clone(){try{WordDocument doc = (WordDocument)super.clone();doc.mText = this.mText;//对mImages对象也调用clone()函数,进行深拷贝doc.mImages = (ArrayList<String>)this.mImages.clone();return doc;}catch(Exception e){}return null;}

修改后在执行上述代码的结果是:

-------- WordDocument构造函数 --------
//originDoc
-------- Word Content Start --------
Text : 这是一篇文档
Images List :
image name : 图片一
image name : 图片二
image name : 图片三
-------- Word Content End --------//doc2
-------- Word Content Start --------
Text : 这是一篇文档
Images List :
image name : 图片一
image name : 图片二
image name : 图片三
-------- Word Content End --------//副本修改后originDoc
-------- Word Content Start --------
Text : 这是一篇文档
Images List :
image name : 图片一
image name : 图片二
image name : 图片三
-------- Word Content End --------//副本修改后doc2
-------- Word Content Start --------
Text : 这是修改过的Doc2文本
Images List :
image name : 图片一
image name : 图片二
image name : 图片三
image name : 这是新添加的图片
-------- Word Content End --------

可以看出现在互不影响,这个叫做深拷贝。

接着上面的疑问,其实String类型在浅拷贝时和引用类型一样,没有单独复制,而是引用同一地址,因为String没有实现cloneable接口,也就是说只能复制引用。(这里我们可以查看源码可以看到,而ArrayList实现了cloneable接口)但是当修改其中的一个值的时候,会新分配一块内存用来保存新的值,这个引用指向新的内存空间,原来的String因为还存在指向他的引用,所以不会被回收,这样,虽然是复制的引用,但是修改值的时候,并没有改变被复制对象的值。

所以在很多情况下,我们可以把String在clone的时候和基本类型做相同的处理,只是在equals时注意一些就行了。

原型模式是非常简单的一个模式,它的核心问题就是对原始对象进行拷贝,在这个模式的使用过程中需要注意的一点就是:深、浅拷贝的问题。在开发过程中,为了减少错误,作者建议使用该模式时尽量使用深拷贝,避免操作副本时影响原始对象的问题。

七、Android源码中的原型模式

示例代码:

    Uri uri = Uri.parse("smsto:110");Intent intent = new Intent(Intent.ACTION_SEND,uri);intent.putExtra("sms_body", "The SMS text");//克隆Intent intent2 = (Intent)intent.clone();startActivity(intent2);

八、总结

原型模式本质上就是对象的拷贝,与C++中的拷贝构造函数有些类似,它们之间容易出现的问题也都是深拷贝、浅拷贝。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。

优点:

  • (1)原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可能更好的体现其优点。

  • (2)还有一个重要的用途就是保护性拷贝,也就是对某个对象对外可能是只读的,为了防止外部对这个只读对象的修改,通常可以通过返回一个对象拷贝的形式实现只读的限制。

缺点:

  • (1)这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的,在实际开发中应该注意这个潜在问题。优点是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。

  • (2)通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能够获得效率上的提升。

Android设计模式之——原型模式相关推荐

  1. android设计模式之原型模式

    定义 使用拷贝来创建一个新的对象 用原型实例指定创建对象的种类,通过拷贝这些圆心来创建新的实例 背景 其实在android的源码里面,我们使用很多的Intent,就使用了原型设计模式,使用原型设计模式 ...

  2. 【GOF23设计模式】原型模式

    [GOF23设计模式]原型模式 来源:http://www.bjsxt.com/  一.[GOF23设计模式]_原型模式.prototype.浅复制.深复制.Cloneable接口  浅复制 1 pa ...

  3. 乐在其中设计模式(C#) - 原型模式(Prototype Pattern)

    [索引页] [源码下载] 乐在其中设计模式(C#) - 原型模式(Prototype Pattern) 作者:webabcd 介绍 用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象. ...

  4. 教你如何一篇博客读懂设计模式之—--原型模式

    教你如何一篇博客读懂设计模式之----原型模式 what:是什么 原型模式: 用于创建重复的对象,既不用一个属性一个属性去set和get,又不影响性能,原型模式产生的对象和原有的对象不是同一个实例,他 ...

  5. 二十三种设计模式之原型模式

    今天继续探讨GOF二十三种设计模式的原型模式,原型模式也是属于创建型模式的一种 原型模式通俗的讲就是对象复制的过程,即通过一个原型对象,我可以得到一个该对象的克隆. 下面来看下原型模式的第一种写法-- ...

  6. C#设计模式(6)——原型模式(Prototype Pattern)

    一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在 ...

  7. 设计模式回顾——原型模式(C++)

    文章目录 1 前言 2 什么是原型模式 2.1 原型模式组成 2.2 原型模式UML图 2.3 原型模式作用 3 原型模式优缺点 4 什么地方使用原型模式 5 原型模式实现 6 原型模式与构造函数 1 ...

  8. 设计模式之 原型模式

    原型模式应用场景举例:  GG和MM经常在QQ上聊天,但是GG打字的速度慢如蜗牛爬行,每次MM在瞬间完成恢复或者问候是,GG都会很紧张的去尽力快速打字,尽管如此,还是让MM有些不高心,MM说回复信息这 ...

  9. 北京Java培训 | java设计模式之原型模式

    克隆羊问题 现在有一只羊tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和tom羊 属性完全相同的10只羊. 传统方式解决克隆羊问题 传统的方式的优缺点 1) 优点是比较好理解,简 ...

最新文章

  1. mysql字段定义成text类型的严重影响查询性能
  2. Anaconda 使用的一些体验与困惑
  3. CTFshow php特性 web114
  4. C#遍历文件读取Word内容以及使用BackgroundWorker对象打造平滑进度条
  5. linux进程中对信号的屏蔽,linux进程中的信号屏蔽
  6. MyBatis框架 多表联合查询实现
  7. java增强型for报错_Java自学-数组 增强型for循环
  8. 什么是面向对象对象,什么是面向过程,什么是面向对象思想。
  9. win7锁定桌面计算机图标,Win7锁定桌面图标的详细步骤(图文)
  10. K3 CLOUD返工生产成本方案——循环计算
  11. 投影幕布尺寸计算器_投影幕布:投影幕布的尺寸计算
  12. 实验室信息管理系统(LIMS)软件大盘点
  13. python网络数据采集 第二版_Python网络数据采集 (影印版)第2版
  14. Python编程:根据经纬度生成并调用地图
  15. 路由器网口1一直闪烁正常吗_路由器灯怎么闪才正常
  16. ODU帧转OTU帧流程
  17. 美国基础设施法案对该国加密矿业会产生什么影响?
  18. 2020最新手机百度云不限速教程,下载速度10M/S,比会员还快
  19. 计算机网络的基础知识
  20. 实现企业微信自动登录

热门文章

  1. 【转】WPF之路-常用布局控件一
  2. 第十四节: 介绍四大并发集合类并结合单例模式下的队列来说明线程安全和非安全的场景及补充性能调优问题。
  3. java对象间的转型,详细讲述Java中的对象转型
  4. 大疆无人机高程不准_大疆消费级无人机快速倾斜摄影测量实践
  5. ipython jupyter区别_ipython jupyter notebook中显示图像和数学公式实例
  6. 正则表达式:获取一串字符串中,某个字符串到某个字符串之间的字符串,不包含左右,只取中间
  7. 查看linux硬核上的线程,给大家分享一点基础硬核知识哦 Linux的基础指令操作Lin...
  8. 【EOJ Monthly 2019.02 - A】回收卫星(交互题型,二分)
  9. java面向对象编程集合边框_JAVA 面向对象 集合框架
  10. python wmi mac变动_Python WMI参数反转