文章目录

  • 定义
  • 特点
  • 类型
  • 适用场景
  • 优点
  • 缺点
  • 扩展
  • coding
    • 目标类实现克隆接口原型模式
    • 抽象类实现克隆接口原型模式
    • 浅克隆和深克隆
    • 克隆破坏单例
  • 源码解析
    • Object的clone方法
    • 实现cloneable接口的类
    • ArrayList实现了Cloneable接口并重写了clone方法
    • HashMap实现了Cloneable接口并重写了clone方法

定义

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

特点

不需要知道任何的创建细节,并且不调用构造函数

类型

创建型

适用场景

  • 类初始化消耗较多资源
    一旦我们创建一个类消耗资源过多的时候,并且还要创建大量的资源多的类的对象的时候
  • new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
    数据准备也就是给属性赋值,还有就是关于里面方法访问权限等
  • 构造函数比较复杂
  • 循环体中生产大量的对象时

优点

  • 原型模式性能比直接new一个对象性能高
  • 简化创建过程

缺点

  • 必须配备克隆方法
    如果没有配备Object的clone方法的话,这个模式也不会生效,这个模式的核心就是克隆方法。通过方法进行对象的拷贝。java提供了一个cloneable的接口来标识这个对象是可拷贝的,为什么要标识呢?在jvm中具有这个标识的对象才有可能被拷贝,是有可能被拷贝,怎么样才能一定被拷贝呢?我们还要覆盖Object的克隆方法,所以这个缺点必须配备clone方法
  • 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险
  • 深拷贝、浅拷贝要运用得当
    在对复杂对象进行深拷贝和浅拷贝时候,一定要运用得当,否则很容易引入风险,在对复杂的对象进行改造时候,很容易引入风险的

扩展

  • 深克隆
    对于引用类型,如果需要它们指向不同的对象,我们一定要使深克隆,而深克隆对于某一个对象的引用类型的时候,我们要显式的去写对于那个属性进行深克隆
  • 浅克隆
    实现比较简单

coding

我们有个视频网站经常给学生发中奖的邮件,代码如下

public class Mail {private String name;private String emailAddress;private String content;public Mail(){System.out.println("Mail class Constructor");}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getEmailAddress() {return emailAddress;}public void setEmailAddress(String emailAddress) {this.emailAddress = emailAddress;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}
}
public class MailUtil {public static void sendMail(Mail mail){String outputContent = "向{0}同学,邮件地址:{1},邮件内容{2}发送邮件成功";System.out.println(MessageFormat.format(outputContent,mail.getName(),mail.getEmailAddress(),mail.getContent()));}public static void saveOriginMailRecord(Mail mail){System.out.println("存储originMail记录,originMail:"+mail.getContent());}
}

应用类

public class Test {public static void main(String[] args) {Mail mail=new Mail();mail.setContent("初始化模板");for(int i=0;i<10;i++){mail.setName("姓名"+i);mail.setEmailAddress("姓名"+i+"@imooc.com");mail.setContent("恭喜您,此次慕课网活动中奖了");MailUtil.sendMail(mail);}MailUtil.saveOriginMailRecord(mail);}
}

运行结果

Mail class Constructor
向姓名0同学,邮件地址:姓名0@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
向姓名1同学,邮件地址:姓名1@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
向姓名2同学,邮件地址:姓名2@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
向姓名3同学,邮件地址:姓名3@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
向姓名4同学,邮件地址:姓名4@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
向姓名5同学,邮件地址:姓名5@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
向姓名6同学,邮件地址:姓名6@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
向姓名7同学,邮件地址:姓名7@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
向姓名8同学,邮件地址:姓名8@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
向姓名9同学,邮件地址:姓名9@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
存储originMail记录,originMail:恭喜您,此次慕课网活动中奖了

目标类实现克隆接口原型模式

我们假设这个mail对象的创建、赋值是很耗性能的,在这个循环中又产生大量的对象,那这时候就需要引入原型模式是非常合适的

改造mail类

/**
* 实现cloneable接口
*/
public class Mail implements Cloneable{private String name;private String emailAddress;private String content;public Mail(){System.out.println("Mail class Constructor");}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getEmailAddress() {return emailAddress;}public void setEmailAddress(String emailAddress) {this.emailAddress = emailAddress;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}/*** 增加clone方法*/@Overrideprotected Object clone() throws CloneNotSupportedException {System.out.println("clone mail object");return super.clone();}
}

应用类

public class Test {public static void main(String[] args) throws CloneNotSupportedException {Mail mail=new Mail();mail.setContent("初始化模板");for(int i=0;i<10;i++){Mail mailTemp= (Mail) mail.clone();mailTemp.setName("姓名"+i);mailTemp.setEmailAddress("姓名"+i+"@imooc.com");mailTemp.setContent("恭喜您,此次慕课网活动中奖了");MailUtil.sendMail(mailTemp);}MailUtil.saveOriginMailRecord(mail);}
}

运行结果

Mail class Constructor
clone mail object
向姓名0同学,邮件地址:姓名0@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
clone mail object
向姓名1同学,邮件地址:姓名1@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
clone mail object
向姓名2同学,邮件地址:姓名2@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
clone mail object
向姓名3同学,邮件地址:姓名3@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
clone mail object
向姓名4同学,邮件地址:姓名4@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
clone mail object
向姓名5同学,邮件地址:姓名5@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
clone mail object
向姓名6同学,邮件地址:姓名6@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
clone mail object
向姓名7同学,邮件地址:姓名7@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
clone mail object
向姓名8同学,邮件地址:姓名8@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
clone mail object
向姓名9同学,邮件地址:姓名9@imooc.com,邮件内容恭喜您,此次慕课网活动中奖了发送邮件成功
存储originMail记录,originMail:初始化模板

抽象类实现克隆接口原型模式

public class A implements Cloneable {@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class B extends A {public static void main(String[] args) throws CloneNotSupportedException {B b=new B();b.clone();}
}

这种方式也比较常见,如果我们的业务模型,可以抽象出来的话也可以使用这种方式,但是实际业务开发中使用上面一种还是较多,直接让目标类来实现克隆接口

浅克隆和深克隆

原型模式虽然简单,但是用不好的话,很容易产生bug

public class Pig implements Cloneable{private String name;private Date birthday;public Pig(String name, Date birthday) {this.name = name;this.birthday = birthday;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return "Pig{" +"name='" + name + '\'' +", birthday=" + birthday +'}'+super.toString();}
}

应用类

public class Test {public static void main(String[] args) throws CloneNotSupportedException {Date birthday=new Date(0L);Pig pig1=new Pig("佩奇",birthday);Pig pig2= (Pig) pig1.clone();System.out.println(pig1);System.out.println(pig2);}
}

运行结果

Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@2c7b84de
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@3fee733d
修改pig1的生日后
Pig{name='佩奇', birthday=Wed Apr 04 19:51:06 CST 2181}com.design.pattern.creational.prototype.clone.Pig@2c7b84de
Pig{name='佩奇', birthday=Wed Apr 04 19:51:06 CST 2181}com.design.pattern.creational.prototype.clone.Pig@3fee733d

看到重新设置完pig1的生日后,Pig@2c7b84de生效了,但是pig2 Pig@3fee73d也变成了2181年,这个就很神奇了,这是默认情况Pig类实现的是浅克隆,那深克隆要怎么做呢?只需要修改Pig类,在clone时候,把生日也给克隆了

@Override
protected Object clone() throws CloneNotSupportedException {Pig pig= (Pig) super.clone();//深克隆pig.birthday= (Date) pig.birthday.clone();return pig;
}

对于Pig里面这种生日引用对象也得单独进行克隆,所以我们在使用原型模式的时候,这个坑一定要注意,否则很容易引起bug的。
我们再运行一次

Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@2c7b84de
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@3fee733d
修改pig1的生日后
Pig{name='佩奇', birthday=Wed Apr 04 19:51:06 CST 2181}com.design.pattern.creational.prototype.clone.Pig@2c7b84de
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}com.design.pattern.creational.prototype.clone.Pig@3fee733d

前面的2个就不看了,就看后面两个,看到修改了pig1的生日后,pig2生日并没有改变
这个非常重要,原型模式简单但是有坑,所以我们在使用的时候,一定要注意深克隆和浅克隆,对于这种引用类型使用的时候一定要注意深克隆还是浅克隆它,对于引用类型使用时还是建议克隆它为好,否则就是在为项目埋坑

克隆破坏单例

原型模式有个小彩蛋,对于原型模式的理解,想象一下如果和单例模式结合起来应用的话会怎样呢?
这个在面试的时候也有可能会被问到如何破坏单例模式,通过原型模式里面的克隆也是方法

/*** 饿汉式*/
public class HungrySingleton implements Serializable ,Cloneable{private final static HungrySingleton hungrySingleton=new HungrySingleton();private HungrySingleton(){if(hungrySingleton!=null){throw new RuntimeException("单例构造器禁止反射调用");}}public static HungrySingleton getInstance(){return hungrySingleton;}/*** 增加该方法,返回这个单例对象* @return*/private Object readResolve(){return hungrySingleton;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class Test {public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {HungrySingleton hungrySingleton=HungrySingleton.getInstance();Method method=hungrySingleton.getClass().getDeclaredMethod("clone");method.setAccessible(true);HungrySingleton cloneHungrySingleton= (HungrySingleton) method.invoke(hungrySingleton);System.out.println(hungrySingleton);System.out.println(cloneHungrySingleton);}
}

运行结果

com.design.pattern.creational.prototype.singleton.HungrySingleton@2626b418
com.design.pattern.creational.prototype.singleton.HungrySingleton@5a07e868

看到两个对象不是一个,如何防止克隆对单例模式的破坏呢?这要怎么办呢?

  1. 可以让HungrySingleton不去实现cloneable接口

  2. 修改clone方法

    @Override
    protected Object clone() throws CloneNotSupportedException {return getInstance();
    }
    

    运行结果

    com.design.pattern.creational.prototype.singleton.HungrySingleton@2626b418
    com.design.pattern.creational.prototype.singleton.HungrySingleton@2626b418
    

源码解析

Object的clone方法

 protected native Object clone() throws CloneNotSupportedException;

实现cloneable接口的类


看到上图都是实现了cloneable接口的类,我们看下mybatis里的关于cache使用的Cachekey

    public CacheKey clone() throws CloneNotSupportedException {CacheKey clonedCacheKey = (CacheKey)super.clone();clonedCacheKey.updateList = new ArrayList(this.updateList);return clonedCacheKey;}

ArrayList实现了Cloneable接口并重写了clone方法

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

看到实现了Cloneable接口,再看看clone方法是如何实现的

public Object clone() {try {ArrayList<?> v = (ArrayList<?>) super.clone();v.elementData = Arrays.copyOf(elementData, size);v.modCount = 0;return v;} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError(e);}
}

看到使用Arrays.copyOf方法,将里面的元素copy了一份

HashMap实现了Cloneable接口并重写了clone方法

public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {
    @Overridepublic Object clone() {HashMap<K,V> result;try {result = (HashMap<K,V>)super.clone();} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError(e);}result.reinitialize();result.putMapEntries(this, false);return result;}

对于原型模式,在使用过程中一定要注意,它是创建的对象和原来的对象是否是同一个,即它是创建出来的一个对象,还是只是创建了一个引用,也就是要把深克隆和浅克隆要应用好

六、简单又有坑的原型模式相关推荐

  1. Android设计模式之——原型模式

    一.介绍 原型模式是一个创建型的模式.原型二字表明了该模型应该有一个样板实例,用户从这个样板对象中复制出一个内部属性一致的对象,这个过程也就是我们俗称的"克隆".被复制的实例就是我 ...

  2. (二十三)原型模式详解(clone方法源码的简单剖析)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 原型模式算是JAVA中最简单 ...

  3. 设计模式(六)原型模式

    一.原型模式的作用? 1.基本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例的话,就需要使用原型模式. 2.用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.这个其实和C ...

  4. 设计模式(六) : 创建型模式--原型模式

    在说原型模式之前,我们先来看java里面的深复制和浅复制: 1. 浅复制:被复制的对象的所有变量都持有和原来对象的变量相同的值,而所有的对其他对象的引用都指向原来的对象. 2. 深复制:被复制对象的所 ...

  5. 第六章 Caché 设计模式 原型模式

    文章目录 第六章 Caché 设计模式 原型模式 定义 使用场景 优点 结构图 描述 示例 初级写法 缺点 中级写法 缺点 高级写法 (浅复制) 浅复制 深复制 完整示例 简历类(复制类) 对象类(工 ...

  6. 设计模式(三):传统模式VS简单原型模式及其优缺点

    概念 原型模式是一个创建型的模式.用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象.原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效 ...

  7. Android Ap 开发 设计模式第六篇:原型模式

    Prototype Pattern 名称由来 不是利用类来产生实例对象,而是从一个对象实例产生出另一个新的对象实例 ,根据被视为原型的对象实例 ,建立起的另一个新的对象实例就称为原型模式(Ptotot ...

  8. 设计模式学习笔记(六)原型模式以及深浅拷贝的区别

    原型模式也是创建对象的一种方式,它一般用在这样的场景:系统中存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂而且耗费资源.这个时候使用原型模式的克隆方式,能够节省不少时间. ...

  9. 原型模式及简单Java案例代码实现

    说明:本文是<大话设计模式>的学习记录及结合网上相关信息编写,原书代码例子采用C#编写,本文采用Java稍加改写.如有不当,欢迎指正,共同进步. 1.原型方法模式概述: 原型模式(Patt ...

  10. JavaScript 面向对象 (prototype 原型模式)

    一. JavaScript 设计思想 1994年,网景公司(Netscape)发布了Navigator浏览器0.9版.这是历史上第一个比较成熟的网络浏览器,轰动一时.但是,这个版本的浏览器只能用来浏览 ...

最新文章

  1. CDataBaseEngineSink::OnRequestPlatformParameter 数据库异常:查询超时已过期 [ 0x80040e31 ]...
  2. glCullFace,GL_CULL_FACE
  3. epoll使用详解(精髓)
  4. rsync 常用命令及格式
  5. 岗位内推 | 微软亚洲互联网工程院自然语言处理组招聘算法研究实习生
  6. K - Triangle 计蒜客 - 42405
  7. PHP 规划(收藏的一些好博文)
  8. 安装LR提示“此计算机缺少 vc2005_sp1_with_atl_fix_redist,请安装所有缺少的必要组件,然后重新运行此安装“
  9. 淡入淡出效果 (jQuery)
  10. Servlet学习笔记
  11. validator校验注解
  12. JavaScript-关闭窗口(window.close)
  13. 谈谈百度贴吧、豆瓣小组以及BBS这个产品形态
  14. Gemini.Workflow 双子工作流入门教程二:定义流程:流程节点介绍
  15. 求高手指点,发现98五笔有几个字三级简码和四级简码完全不同,不知道使用98五笔的亲们怎么用的?
  16. 第十六周 结构体复数计算
  17. OpenCV系列之级联分类器 | 六十一
  18. 国产单片机MCU DSH550 ,可应用于中央空调温控器上
  19. html画布标签的使用
  20. phone开发基础教程

热门文章

  1. MooTools 1.4 源码分析 - Fx
  2. k8s搭建dashboard可视化界面
  3. ConcurrentHashMap深入分析(特别重要)
  4. 【Spring-tx】AutoProxyRegistrar类
  5. POI合并单元格时CellRangeAddress类提示过时之解决
  6. Hibernate二级缓存以及ehcache的搭建配置
  7. 【译】给小白准备的Web架构基础知识
  8. 网站开发流程以及HTML5简介(十)
  9. ubuntu卸载vmware player
  10. javascript基础(对象继承与引用)