Java设计模式之原型模式详解
作者:小小鱼儿小小林 发布时间:2021-09-06 19:04:49
一、前言
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类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();
}
}
}
运行结果:
这就是深复制和浅复制以及原型模式的使用
来源:https://blog.csdn.net/qq_27471405/article/details/116309878


猜你喜欢
- 本文实例为大家分享了java实现马踏棋盘的具体代码,供大家参考,具体内容如下马踏棋盘算法介绍8X8棋盘,马走日字,要求每个方格只进入一次,走
- 目录一、二叉树的顺序存储1.堆的存储方式2.下标关系二、堆(heap)1.概念2.大/小 根堆2.1小根堆2.2大根堆3.建堆操作3.1向下
- 汉诺塔问题是学习递归的入门问题,这里用C#简单实现了一个汉诺塔之间传递盘子的小程序通过简单绘图实现盘子在几个塔之间的转换:namespace
- 如下所示:from jpype import *jvmPath = getDefaultJVMPath()jars = ["./F
- C#字符集编码的使用ASCII:西欧字符集GB2312:国家简体中文字符集,兼容ASCII。BIG5:统一繁体字编码GBK:它是GB2312
- 本文实例展示了C#自定义函数NetxtString实现生成随机字符串的方法,在进行C#项目开发中非常实用!分享给大家供大家参考。一、生成随机
- 日记基础操作编程期调试代码运营期记录信息记录日常运营重要信息(峰值流量,平均响应时长...)记录应用报错信息(错误堆栈)记录运维过程数据(扩
- Understanding AsyncTaskAsyncTask是Android 1.5 Cubake加入的用于实现异步操作的一个类,在此之
- 本文实例讲述了C#创建临时文件的方法。分享给大家供大家参考。具体分析如下:C#可以通过Path.GetTempFileName获得一个临时文
- 在网上虽然看到了方法,但是处理感觉很复杂,我的办法,老实说,是突然试一下试到的,哈哈QWQOK,开始说明如何整的。效果如上图所示代码如下pa
- 一、概述在写代码之前,我必须得问几个问题:1、ViewGroup的职责是啥?ViewGroup相当于一个放置View的容器,并且我们在写布局
- MyBatis注解实现动态SQL在 Mybatis 中,使用注解可以很方便的进行sql操作,但很多动态 SQL 都是由 xml 配置实现的。
- 前言在上篇文章(Android实现圆弧滑动效果之ArcSlidingHelper篇)中,我们把圆弧滑动手势处理好了,那么这篇文章我们就来自定
- windows系统中的画板工具,有好几种画刷,C#中并没有直接对应可使用的类,只能自己研究。1.画刷原理根据本人对PS的相关功能细心分析,发
- equals 方法是 java.lang.Object 类的方法。有两种用法说明:(1)对于字符串变量来说,使用“==”和“equals()
- 在使用AbstractRoutingDataSource配置多数据源时,发现使用@aspect配置的DataSourceSwitchAspe
- 如果我们做一个小型的web站,而且刚好选择的kotlin 和Spring Boot技术栈,那么上传文件的必不可少了,当然,如果你做一个中大型
- 遇到的问题!注:自定义CommentGenerator的都知道通过实现CommentGenerator接口的一些不足,毕竟只是实现了Comm
- 一般要做正圆形图片,只能是正方形的基础上才能实现,否则就变成椭圆了,下面说说如何使长方形的图片生成正圆形图片废话不多说,没图没真相,先上图吧
- 在Controller层时,往往会需要校验或验证某些操作,而在每个Controller写重复代码,工作量比较大,这里在Springboot项