六、简单又有坑的原型模式
文章目录
- 定义
- 特点
- 类型
- 适用场景
- 优点
- 缺点
- 扩展
- 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
看到两个对象不是一个,如何防止克隆对单例模式的破坏呢?这要怎么办呢?
可以让HungrySingleton不去实现cloneable接口
修改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;}
对于原型模式,在使用过程中一定要注意,它是创建的对象和原来的对象是否是同一个,即它是创建出来的一个对象,还是只是创建了一个引用,也就是要把深克隆和浅克隆要应用好
六、简单又有坑的原型模式相关推荐
- Android设计模式之——原型模式
一.介绍 原型模式是一个创建型的模式.原型二字表明了该模型应该有一个样板实例,用户从这个样板对象中复制出一个内部属性一致的对象,这个过程也就是我们俗称的"克隆".被复制的实例就是我 ...
- (二十三)原型模式详解(clone方法源码的简单剖析)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 原型模式算是JAVA中最简单 ...
- 设计模式(六)原型模式
一.原型模式的作用? 1.基本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例的话,就需要使用原型模式. 2.用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.这个其实和C ...
- 设计模式(六) : 创建型模式--原型模式
在说原型模式之前,我们先来看java里面的深复制和浅复制: 1. 浅复制:被复制的对象的所有变量都持有和原来对象的变量相同的值,而所有的对其他对象的引用都指向原来的对象. 2. 深复制:被复制对象的所 ...
- 第六章 Caché 设计模式 原型模式
文章目录 第六章 Caché 设计模式 原型模式 定义 使用场景 优点 结构图 描述 示例 初级写法 缺点 中级写法 缺点 高级写法 (浅复制) 浅复制 深复制 完整示例 简历类(复制类) 对象类(工 ...
- 设计模式(三):传统模式VS简单原型模式及其优缺点
概念 原型模式是一个创建型的模式.用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象.原型模式多用于创建复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可使程序运行更高效 ...
- Android Ap 开发 设计模式第六篇:原型模式
Prototype Pattern 名称由来 不是利用类来产生实例对象,而是从一个对象实例产生出另一个新的对象实例 ,根据被视为原型的对象实例 ,建立起的另一个新的对象实例就称为原型模式(Ptotot ...
- 设计模式学习笔记(六)原型模式以及深浅拷贝的区别
原型模式也是创建对象的一种方式,它一般用在这样的场景:系统中存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂而且耗费资源.这个时候使用原型模式的克隆方式,能够节省不少时间. ...
- 原型模式及简单Java案例代码实现
说明:本文是<大话设计模式>的学习记录及结合网上相关信息编写,原书代码例子采用C#编写,本文采用Java稍加改写.如有不当,欢迎指正,共同进步. 1.原型方法模式概述: 原型模式(Patt ...
- JavaScript 面向对象 (prototype 原型模式)
一. JavaScript 设计思想 1994年,网景公司(Netscape)发布了Navigator浏览器0.9版.这是历史上第一个比较成熟的网络浏览器,轰动一时.但是,这个版本的浏览器只能用来浏览 ...
最新文章
- CDataBaseEngineSink::OnRequestPlatformParameter 数据库异常:查询超时已过期 [ 0x80040e31 ]...
- glCullFace,GL_CULL_FACE
- epoll使用详解(精髓)
- rsync 常用命令及格式
- 岗位内推 | 微软亚洲互联网工程院自然语言处理组招聘算法研究实习生
- K - Triangle 计蒜客 - 42405
- PHP 规划(收藏的一些好博文)
- 安装LR提示“此计算机缺少 vc2005_sp1_with_atl_fix_redist,请安装所有缺少的必要组件,然后重新运行此安装“
- 淡入淡出效果 (jQuery)
- Servlet学习笔记
- validator校验注解
- JavaScript-关闭窗口(window.close)
- 谈谈百度贴吧、豆瓣小组以及BBS这个产品形态
- Gemini.Workflow 双子工作流入门教程二:定义流程:流程节点介绍
- 求高手指点,发现98五笔有几个字三级简码和四级简码完全不同,不知道使用98五笔的亲们怎么用的?
- 第十六周 结构体复数计算
- OpenCV系列之级联分类器 | 六十一
- 国产单片机MCU DSH550 ,可应用于中央空调温控器上
- html画布标签的使用
- phone开发基础教程