设计模式之九原型模式
问题描述
在开发中你也许遇到过大篇幅的使用get或set赋值的场景,例如:
public void setParam(ExamPaperVo vo){ExamPaper examPaper = new ExamPaper();examPaper.setPaperId(vo.getId());examPaper.setLeaveTime(vo.getLeaveTime());examPaper.setOrg(vo.getOrg());...
}
上述代码非常工整,命名非常规范,注释也写的很全面,大家觉得这样的代码优雅吗?以上代码属于“纯体力劳动”。原型模式就能帮助我们解决这样的问题。
概念
原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
原型模式的主要思想是基于现有的对象克隆一个新的对象出来,一般是由对象的内部提供克隆的方法,通过clone方法返回一个对象的副本。
原型模式主要适用于以下场景:
①类初始化消耗资源较多
②使用new生成一个对象需要非常繁琐的过程(数据准备,访问权限等)
③构造函数比较复杂
④在循环体中产生大量对象。
在Spring中,原型模式应用的非常广泛。例如scope=“prototype”,我们经常使用的JSON.parseObject()也是一种原型模式。下面是原型模式的类结构图:
角色
①抽象原型(Prototype)角色:规定了具体原型对象必须实现的接口(如果要提供深拷贝,则必须具有实现clone的规定)
②具体原型(ConcretePrototype):从抽象原型派生而来,是客户程序使用的对象,即被复制的对象,需要实现抽象原型角色所要求的接口。
③客户(Client)角色:使用原型对象的客户程序
案例一:浅克隆
被拷贝对象的所有变量都含有与原对象相同的值,而且对其他对象的引用仍然是指向原来的对象。即浅拷贝只负责当前对象实例,对引用的对象不做拷贝。
package com.hanker.dao.impl;import java.util.ArrayList;
import java.util.List;//原型接口
interface Prototype{Prototype clone();
}
//具体克隆的类
class ContretePrototypeA implements Prototype{private int age;private String name;private List<String> hobbies;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public List<String> getHobbies() {return hobbies;}public void setHobbies(List<String> hobbies) {this.hobbies = hobbies;}@Overridepublic ContretePrototypeA clone() {ContretePrototypeA contretePrototypeA = new ContretePrototypeA();contretePrototypeA.setAge(this.age);contretePrototypeA.setName(this.name);contretePrototypeA.setHobbies(this.hobbies);return contretePrototypeA;}
}
//client类
class Client{private Prototype prototype;public Client(Prototype prototype) {this.prototype=prototype;}public Prototype startClone() {return prototype.clone();}
}
public class PrototypePatternDemo1 {public static void main(String[] args) {// 创建一个具体的克隆对象ContretePrototypeA contretePrototypeA = new ContretePrototypeA();//填充属性,方便测试contretePrototypeA.setAge(18);contretePrototypeA.setName("张无忌");List<String> hobbies = new ArrayList<>();contretePrototypeA.setHobbies(hobbies);System.out.println(contretePrototypeA);//创建Client对象Client client = new Client(contretePrototypeA);ContretePrototypeA cloneObj = (ContretePrototypeA) client.startClone();System.out.println(cloneObj);System.out.println("克隆对象中的引用类型地址值:"+cloneObj.getHobbies());System.out.println("原型对象中的引用类型地址值:"+contretePrototypeA.getHobbies());System.out.println("对象地址比较:"+ (contretePrototypeA.getHobbies() == cloneObj.getHobbies()) );}
}
执行结果:
com.hanker.dao.impl.ContretePrototypeA@6d06d69c
com.hanker.dao.impl.ContretePrototypeA@7852e922
克隆对象中的引用类型地址值:[]
原型对象中的引用类型地址值:[]
对象地址比较:true
从测试结果可以看出,hobbies的引用地址是相同的,意味着复制的不是值,而是引用地址。这样的话,如果我们修改任意一个对象的属性值,则contretePrototypeA和cloneObj的hobbies值都会改变,这就是我们常说的浅克隆。浅克隆只是完整复制了值类型数据,没有复制引用对象。换言之,所有的引用对象仍然指向原来的对象,显示这不是我们想要的结果。
案例二:深克隆
被拷贝对象的所有的变量都含有与原来对象相同的值,除了引用其他对象的变量。引用其他对象的变量将指向一个被拷贝的新对象,而不再是原有被引用对象。即深拷贝把要拷贝的对象所引用的对象也都拷贝了一次。
深拷贝要深入到多少层,是一个不确定的问题。在决定以深拷贝的方式拷贝一个对象的时候,必须决定对间接拷贝的对象是采取浅拷贝还是深拷贝还是继续采用深拷贝。因此,在采取深拷贝时,需要决定多深才算深。此外,在深拷贝的过程中,很可能会出现循环引用的问题。
我们换一个场景,大家都知道齐天大圣,首先它是一个猴子,有七十二般变化,拔一根毫毛就可以吹出千万个猴子,手里还拿着金箍棒,金箍棒可以变大或变小。这就是我们耳熟能详的原型模式的经典体现。
package com.hanker.dao.impl;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;//创建原型猴子类
class Monkey{public int height;public int weight;public Date birthday;
}
//创建引用对象金箍棒类Jingubang
class Jingubang implements Serializable{public float h = 100;public float d = 10;public void big() {this.d *= 2;this.h *= 2;}public void small() {this.d /= 2;this.h /= 2;}
}
//创建具体的对象齐天大圣类
class Qitiandasheng extends Monkey implements Cloneable,Serializable{public Jingubang jingubang;public Qitiandasheng() {//只是初始化this.birthday = new Date();this.jingubang = new Jingubang();}@Overrideprotected Object clone() throws CloneNotSupportedException {return this.deepClone();}//深克隆private Object deepClone() {try {ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);Qitiandasheng copy = (Qitiandasheng) ois.readObject();copy.birthday = new Date();return copy;} catch (Exception e) {e.printStackTrace();return null;}}//浅克隆public Qitiandasheng shallowClone(Qitiandasheng target) {Qitiandasheng qitiandasheng = new Qitiandasheng();qitiandasheng.height = target.height;qitiandasheng.weight = target.weight;qitiandasheng.jingubang = target.jingubang;qitiandasheng.birthday = new Date();return qitiandasheng;}
}
public class PrototypePatternDemo2 {public static void main(String[] args) {Qitiandasheng qitiandasheng = new Qitiandasheng();try {Qitiandasheng clone = (Qitiandasheng) qitiandasheng.clone();System.out.println("深克隆:"+ (qitiandasheng.jingubang == clone.jingubang));} catch (Exception e) {}Qitiandasheng q = new Qitiandasheng();Qitiandasheng n = q.shallowClone(q);System.out.println("浅克隆:" + (q.jingubang == n.jingubang));}
}
执行结果:
深克隆:false
浅克隆:true
克隆破坏单例模式
如果我们克隆的目标对象是单例对象,那么意味着深克隆会破坏单例模式。实际上防止克隆破坏单例模式的解决思路非常简单,禁止深克隆便可。要么我们的单例类不实现Cloneable接口,要么我们重写clone方法,在clone方法中返回单例对象即可。具体代码如下:
@Override
protected Object clone() throws CloneNotSupportedException {return INSTANCE;
}
clone()方法的源码
我们常用的ArrayList类实现了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);}
}
原型管理器
如果一个系统中原型的数目不固定,比如系统中的原型可以被动态的创建或销毁,那么就需要在系统中维护一个当前可用的原型的注册表,这个注册表就被称为原型管理器。其实如果把原型当作资源的话,原型管理器就相当于一个资源管理器,在系统运行的时候初始化,然后运行期间可以动态地添加和销毁资源。从这个角度看,原型管理器就可以相当于一个缓存资源的实现,只不过里面缓存和管理的是原型实例而已。
有了原型管理器后,一般情况下,除了向原型管理器里面添加原型对象的时候是通过new来创建对象,其余时候都是通过向原型管理器请求原型实例,然后通过克隆方法来获取新的对象实例,这就是动态实现原型管理,或者动态切换具体的实现对象实例。
package com.hanker.annotation;import java.util.HashMap;
import java.util.Map;//定义原型接口
interface Prototype{Prototype clone();String getName();void setName(String name);
}
//原型的实现1
class ConcretePrototype1 implements Prototype{private String name;@Overridepublic Prototype clone() {ConcretePrototype1 prototype = new ConcretePrototype1();prototype.setName(this.name);return prototype;}@Overridepublic String getName() {return this.name;}@Overridepublic void setName(String name) {this.name = name;}public String toString() {return "Now in Prototype1, name = "+name;}
}
//原型的实现2
class ConcretePrototype2 implements Prototype{private String name;@Overridepublic Prototype clone() {ConcretePrototype2 prototype = new ConcretePrototype2();prototype.setName(this.name);return prototype;}@Overridepublic String getName() {return this.name;}@Overridepublic void setName(String name) {this.name = name;}public String toString() {return "Now in Prototype2, name = "+name;}
}
//原型管理器
class PrototypeManager{//用来记录原型的编号和原型实例的对应关系private static Map<String,Prototype> map = new HashMap<>();//私有化构造方法,避免外部无谓的创建实例private PrototypeManager() {}/*** 向原型管理器里面添加或者修改某个原型实例* @param id 原型编号* @param p 原型类型*/public synchronized static void setPrototype(String id,Prototype p) {map.put(id, p);}/*** 从原型管理器里面删除某个原型注册* @param id 原型编号*/public synchronized static void removePrototype(String id) {map.remove(id);}/*** 获取某个原型编号对应的原型实例* @param id 原型编号* @return 原型编号对应的原型实例* @throws Exception 如果原型编号对应的原型实例不存在,抛出异常*/public synchronized static Prototype getPrototype(String id) throws Exception {Prototype prototype = map.get(id);if(prototype == null) {throw new Exception("您希望获取的原型还没有注册或已被销毁");}return prototype;}
}
public class PrototypePatternDemo3 {public static void main(String[] args) {try {//初始化原型管理器Prototype p1 = new ConcretePrototype1();PrototypeManager.setPrototype("Prototype1", p1);//获取原型来创建对象Prototype p3 = PrototypeManager.getPrototype("Prototype1").clone();p3.setName("张三");System.out.println("第一个实例:"+p3);//有人动态切换了实现Prototype p2 = new ConcretePrototype2();PrototypeManager.setPrototype("Prototype1", p2);//重新获取原型来创建对象Prototype p4 = PrototypeManager.getPrototype("Prototype1").clone();p4.setName("李四");System.out.println("第二个实例:"+p4);//有人注销了这个原型PrototypeManager.removePrototype("Prototype1");//再次获取原型来创建对象Prototype p5 = PrototypeManager.getPrototype("Prototype1").clone();p4.setName("王五");System.out.println("第三个实例:"+p5);} catch (Exception e) {System.err.println(e.getMessage());}}
}
执行结果:
第一个实例:Now in Prototype1, name = 张三
第二个实例:Now in Prototype2, name = 李四
您希望获取的原型还没有注册或已被销毁
本质
原型模式的本质: 克隆生成对象
优缺点
优点
A、原型模式对客户隐藏了具体的产品类
B、运行时刻增加和删除产品: 原型模式允许只通过客户注册原型实例就可以将一个新的具体产品类并入系统。
C、改变值以指定新对象: 高度动态的系统允许通过对象复合定义新的行为。如通过为一个对象变量指定值并且不定义新的类。通过实例化已有类并且将实例注册为客户对象的原型,就可以有效定义新类别的对象。客户可以将职责代理给原型,从而表现出新的行为。
D、改变结构以指定新对象:许多应用由部件和子部件来创建对象。
E、减少子类的构造,Prototype模式克隆一个原型而不是请求一个工厂方法去产生一个新的对象。
F、用类动态配置应用 一些运行时刻环境允许动态将类装载到应用中。
G、使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
H、使用原型模式的另一个好处是简化对象的创建,使得创建对象很简单。
缺点
原型模式的主要缺陷是每一个抽象原型Prototype的子类都必须实现clone操作,实现clone函数可能会很困难。当所考虑的类已经存在时就难以新增clone操作,当内部包括一些不支持拷贝或有循环引用的对象时,实现克隆可能也会很困难的。
设计模式之九原型模式相关推荐
- Java描述设计模式(05):原型模式
一.原型模式简介 1.基础概念 原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象. 2.模式结构 原型模式要求对象实现一个 ...
- 设计模式学习笔记-原型模式
一.概述 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象: 二.模式中的角色 Prototype:声明一个克隆自身的接口: ConcretePrototype:实现一个克隆自身的操作: ...
- 【设计模式 06】原型模式(克隆??)
原型模式(clone?) Prototype pattern refers to creating duplicate object while keeping performance in mind ...
- java 设计模式 优缺点_java设计模式2:原型模式(机制\优缺点分析\使用场景)...
1. 原型模式实现机制 原型模式在设计模式中相对比较简单,它直接通过实现 Cloneable接口,再重写 clone()方法返回想要的对象就OK 了. 一起来看下代码 : public class P ...
- Java设计模式5:原型模式
原型模式 原型模式属于对象的创建模式,通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的用意. 原型模式结构 原型模式要求对象实现一个 ...
- 设计模式之【原型模式】,深入理解深拷贝与浅拷贝
文章目录 一.什么是原型模式 二.原型模式实现方式 1.传统方式 2.原型模式 熟悉浅拷贝和深拷贝 浅拷贝实现对象克隆 深拷贝实现对象克隆 一.什么是原型模式 原型模式: 用一个已经创建的实例作为原型 ...
- 【设计模式自习室】原型模式
前言 <设计模式自习室>系列,顾名思义,本系列文章带你温习常见的设计模式.主要内容有: 该设计模式的详细介绍,包括: 引子,意图(大白话解释) 类图,时序图(理论规范) 该模式的代码示例: ...
- java设计模式之五(原型模式)
什么是原型模式? 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 当我们程序中有几个相似但又不 ...
- 设计模式 之美 -- 原型模式
文章目录 1. 解决问题 2. 应用场景 3. 实现方式 C++实现 C语言实现 4. 缺点 5. 和其他三种创建模式的对比(单例,工厂,建造者) 1. 解决问题 如果对象的创建成本较大,而同一个类的 ...
最新文章
- 如何在命令行上创建符合特定规范的密码?
- TweetBot TabBar
- 你所需要的java基础篇深入解析大汇总
- C++ Opengl纹理贴图源码
- 关于SharePoint中管理列表项权限
- 44特征02——相似对角化与方幂、代数重数与几何重数、可对角化的概念、相似对角化的条件、矩阵方幂的计算
- docker安装centos7镜像
- 电脑只能上微信不能打开网页_电脑可以上微信,但是网页打不开怎么解决
- tc简单开发。窗口类。在窗口初始化时,使用窗口隐藏()
- android apk 微信登入_Android实现使用微信登录第三方APP的方法
- Debian 10 安装fonts-noto-cjk
- H264解码器源码(Android 1.6 版)
- 2022年软件测试人员必读的经典书籍推荐(附电子版)
- 机器学习 梯度到底是什么?
- docker 部署nginx,挂载nginx.conf
- 记一次Very Animation动画插件使用
- 郑大计算机专业多少分,2020年,郑大各专业分数线公布,里面门道很多,给你们一一分析...
- SMTP协议解读以及如何使用SMTP协议发送电子邮件
- gitlab ssh配置
- javaSE——Java基础类库