Java设计模式之原型模式详解
一、前言
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。在Java中,复制对象是通过clone()实现的,先创建一个原型类,通过实现Cloneable 接口
public class Prototype implements Cloneable { public Object clone() throws CloneNotSupportedException { Prototype proto = (Prototype) super.clone(); return proto; } }
只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的,说明这个方法实现并不是使用java语言,是底层C实现阿达
至于cloneA或者cloneB名字可以任意取,是因为要你主动去调用的,所以你名字取成什么,你调用的时候就调用该名字就可以了
二、优点及适用场景
使用原型模式创建对象比直接new一个对象在性能上要好的多,因为上面我也提到过,Object类的clone()是native的,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。
因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。
三、原型模式的注意事项
使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone()来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。
说到这里,就得顺便提一下单例模式,在单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。
四、浅复制和深复制
另外还得知道两个特别重要的概念 : 浅复制 深复制
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而数组、容器对象、引用对象等都不会拷贝,指向的还是原对象所指向的地址。浅拷贝实现 Cloneable,重写clone方法
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。深拷贝是通过实现 Serializable 读取二进制流
五、浅复制demo演示
首先我们创建一个抽象原型类 Animal.class,实现了Cloneable接口,并且重写了clone方法
package cn.zygxsq.design.module.prototypePattern; /** * Created by yjl on 2021/4/30. * 动物类 * 原型模式:博文介绍链接:https://blog.csdn.net/qq_27471405/article/details/116309878 */ public abstract class Animal implements Cloneable{ private String id; public String name; abstract void shout(); public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 浅复制 */ public Object clone() throws CloneNotSupportedException { Animal clone = (Animal) super.clone(); return clone; } }
再创建两个实现类
Dog.class 和Cat.class
package cn.zygxsq.design.module.prototypePattern; /** * Created by yjl on 2021/4/30. */ public class Dog extends Animal { public Dog(){ name = "狗狗"; } @Override public void shout() { System.out.println("我的叫声是:汪汪汪"); } }
package cn.zygxsq.design.module.prototypePattern; /** * Created by yjl on 2021/4/30. */ public class Cat extends Animal { public Cat(){ name = "猫猫"; } @Override public void shout() { System.out.println("我的叫声是:喵喵喵"); } }
然后创建一个数据缓存类,用户存储从数据库中获取到的大对象数据或者曾经使用过的大对象数据
以后下一次想要再次对这个对象数据操作的时候,直接从缓存里获取并且clone一个
package cn.zygxsq.design.module.prototypePattern; import com.google.common.collect.Maps; import org.springframework.beans.factory.InitializingBean; import java.util.HashMap; import java.util.Hashtable; import java.util.concurrent.ConcurrentMap; /** * Created by yjl on 2021/4/30. * 缓存类 用于加载一些数据库的缓存的数据 */ public class DataCache /*implements InitializingBean*/{ //正常的情况是 实现 InitializingBean ,用于web服务启动的时候加载数据 // 这里测试由于不是web服务,所以就模拟加载数据 private static ConcurrentMap<String, Animal> animalCache = Maps.newConcurrentMap(); public static Animal getAnimal(String id) throws Exception{ Animal cache = animalCache.get(id); return (Animal) cache.clone(); } public static void init(){ Dog dog = new Dog(); String dogid = "111"; dog.setId(dogid); animalCache.put(dogid,dog); Dog dog2 = new Dog(); String dogid2 = "222"; dog2.setId(dogid2); animalCache.put(dogid2,dog2); Cat cat = new Cat(); String catid = "333"; cat.setId(catid); animalCache.put(catid,cat); } }
最后咱们开始测试
先是从数据库里加载缓存,然后要从缓存里获取数据,并且的到的是一个个clone出来的对象
package cn.zygxsq.design.module.prototypePattern; import com.alibaba.fastjson.JSON; /** * Created by yjl on 2021/4/30. * 测试主类 浅复制 * 原型模式:博文介绍链接:https://blog.csdn.net/qq_27471405/article/details/116309878 */ public class TestPrototype { public static void main(String[] args) { DataCache.init(); // 模拟加载数据到缓存中 try { Animal animal = DataCache.getAnimal("111"); System.out.println(animal.getName()+"---"+JSON.toJSONString(animal)); Animal animal222 = DataCache.getAnimal("222"); System.out.println(animal222.getName()+"---"+JSON.toJSONString(animal222)); Animal animal333 = DataCache.getAnimal("333"); System.out.println(animal333.getName()+"---"+JSON.toJSONString(animal333)); animal.shout(); animal222.shout(); animal333.shout(); } catch (Exception e) { e.printStackTrace(); } } }
运行结果:
小伙伴们看的时候,好像没什么问题,确实没什么问题,但是细细一看,还是有一定的问题的
package cn.zygxsq.design.module.prototypePattern; import com.alibaba.fastjson.JSON; /** * Created by yjl on 2021/4/30. * 浅复制遇到的问题 */ public class TestCloneProblem { public static void main(String[] args) { //做完TestPrototype的main方法后,好像觉得浅复制没有什么问题 //那么可以看一下下面的a1的name 和 克隆后的name指向的是同一个地址 DataCache.init(); // 模拟加载数据到缓存中 try { Animal a1 = DataCache.getAnimal("111"); Animal a2 = (Animal)a1.clone(); System.out.println(a1==a2); System.out.println(a1.name == a2.name); } catch (Exception e) { e.printStackTrace(); } } }
运行结果:
大家踩一下 a1的name 和 克隆后的name是什么样的关系呢
大家可以看到,a1的name和a2的name是一样的,由于他们的类型是String,所以他们指向的是同一个地址,名称为“狗狗”的引用地址,大概的样子可以看下图
那怎么样才能不让a1.name 和a2.name不相同呢,也就是完完全全的复制,这个就得用到深复制了
深复制其实用到的就是流复制
可以在clone()的方法定义一个深复制的方法,比如deepClone()
六、深复制demo演示
记住,深复制的时候,方法一定得实现可序列化,Serializable
package cn.zygxsq.design.module.prototypePattern; import java.io.*; /** * Created by yjl on 2021/4/30. * 动物类 * 原型模式:博文介绍链接:https://blog.csdn.net/qq_27471405/article/details/116309878 */ public abstract class Animal implements Cloneable, Serializable{ private String id; public String name; abstract void shout(); public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 浅复制 */ public Object clone() throws CloneNotSupportedException { Animal clone = (Animal) super.clone(); return clone; } /** * 深复制 */ public Object deepClone() { ByteArrayOutputStream byteArrayOutputStream = null; ObjectOutputStream objectOutputStream = null; ByteArrayInputStream byteArrayInputStream = null; ObjectInputStream objectInputStream = null; try { // 序列化 byteArrayOutputStream = new ByteArrayOutputStream(); objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(this);/*将当前对象以对象流的方式输出*/ //反序列化 byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); objectInputStream = new ObjectInputStream(byteArrayInputStream); Animal deepProtoType = (Animal) objectInputStream.readObject(); return deepProtoType; } catch (Exception e) { e.printStackTrace(); return null; } finally { try { byteArrayOutputStream.close(); objectOutputStream.close(); byteArrayInputStream.close(); objectInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
测试一下结果
package cn.zygxsq.design.module.prototypePattern; import com.alibaba.fastjson.JSON; /** * Created by yjl on 2021/4/30. * 测试主类 深复制 * 原型模式:博文介绍链接:https://blog.csdn.net/qq_27471405/article/details/116309878 */ public class TestPrototypeDeepClone { public static void main(String[] args) { DataCache.init(); // 模拟加载数据到缓存中 try { Animal a1 = DataCache.getAnimal("111"); Animal a2 = (Animal)a1.deepClone(); System.out.println(a1==a2); System.out.println(a1.name == a2.name); System.out.println(a1.name); } catch (Exception e) { e.printStackTrace(); } } }
运行结果:
这就是深复制和浅复制以及原型模式的使用
到此这篇关于Java设计模式之原型模式详解的文章就介绍到这了,更多相关Java原型模式内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!
相关文章
- 这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
java 运行报错has been compiled by a more recent version of the Java Runtime
java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01- 这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
- 这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
- 这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
- 说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
- 神马是“解释器模式”?先翻开《GOF》看看Definition:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。在开篇之前还是要科普几个概念: 抽象语法树: 解释器模式并未解释如...2014-06-07
- 这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
- 这篇文章主要为大家介绍了JavaScript设计模式中的装饰者模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-21
- 这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
- 这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
- 这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01
java 画pdf用itext调整表格宽度、自定义各个列宽的方法
这篇文章主要介绍了java 画pdf用itext调整表格宽度、自定义各个列宽的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-31- 这篇文章主要介绍了java正则表达式判断前端参数修改表中另一个字段的值,需要的朋友可以参考下...2021-05-07
Java使用ScriptEngine动态执行代码(附Java几种动态执行代码比较)
这篇文章主要介绍了Java使用ScriptEngine动态执行代码,并且分享Java几种动态执行代码比较,需要的朋友可以参考下...2021-04-15- 这篇文章主要介绍了Java开发实现人机猜拳游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-03
- 这篇文章主要介绍了Java List集合返回值去掉中括号('[ ]')的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-29
Java中lombok的@Builder注解的解析与简单使用详解
这篇文章主要介绍了Java中lombok的@Builder注解的解析与简单使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-01-06- 下面小编就为大家带来一篇java中String类型变量的赋值问题介绍。小编觉得挺不错的。现在分享给大家,给大家一个参考。...2016-03-28
Java 8 Stream 的终极技巧——Collectors 功能与操作方法详解
这篇文章主要介绍了Java 8 Stream Collectors 功能与操作方法,结合实例形式详细分析了Java 8 Stream Collectors 功能、操作方法及相关注意事项,需要的朋友可以参考下...2020-05-20