查看原文
其他

Java设计模式—原型模式(prototype pattern)

素文宅博客 Java精选 2022-08-09


原型模式是一种创建型设计模式,通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。



原型模式要求对象实现一个可以“克隆”自身的接口,该接口通过复制一个实例对象本身来创建一个新的实例。那么原型实例创建新的对象时就无需关心这个实例本身的类型,只要实现克隆自身的方法,就可以通过这个方法来获取新的对象,没必要再去通过new来创建。


原型模式的两种表现形式

原型模式有两种表现形式(仅仅是原型模式的不同实现):简单形式和登记形式。


  简单形式的原型模式



这种模式涉及到三种角色分别如下:


1)客户(Client)角色:客户类提出创建对象的请求。

public class Client { /** * 原型接口对象 */ private Prototype prototype; /** * 构造方法 */ public Client(Prototype prototype){ this.prototype = prototype; } public void operation(Prototype example){ Prototype copyPrototype = prototype.clone();
}}


2)抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。

public interface Prototype{ /** * 克隆自身的方法 */ public Object clone();}


3)具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。

public class ConcretePrototype1 implements Prototype { public Prototype clone(){ Prototype prototype = new ConcretePrototype1(); return prototype; }}
public class ConcretePrototype2 implements Prototype { public Prototype clone(){ //最简单的克隆,新建一个自身对象,由于没有属性就不再复制值了 Prototype prototype = new ConcretePrototype2(); return prototype; }}


  登记形式的原型模式


登记形式相比简单形式多了一个原型管理器(PrototypeManager)角色,该角色的作用是创建具体原型类的对象,并记录每一个被创建的对象。



原型管理器角色保持一个聚集,作为对所有原型对象的登记,这个角色提供必要的方法,供外界增加新的原型对象和取得已经登记过的原型对象。

public class PrototypeManager { /** * 记录原型的编号和原型实例的对应关系 */ private static Map<String,Prototype> map = new HashMap<String,Prototype>(); /**     * 私有化无参构造方法 */ private PrototypeManager(){} /**     * 原型管理器添加或是修改某个原型注册 * @param prototypeId 原型编号 * @param prototype 原型实例 */ public synchronized static void setPrototype(String prototypeId , Prototype prototype){ map.put(prototypeId, prototype); } /**     * 从原型管理器中删除某个原型注册 * @param prototypeId 原型编号 */ public synchronized static void removePrototype(String prototypeId){ map.remove(prototypeId); } /** * 获取某个原型编号对应的原型实例 * @param prototypeId 原型编号 * @return 原型编号对应的原型实例 * @throws Exception 如果原型编号对应的实例不存在,则抛出异常 */ public synchronized static Prototype getPrototype(String prototypeId) throws Exception{ Prototype prototype = map.get(prototypeId); if(prototype == null){ throw new Exception("您希望获取的原型还没有注册或已被销毁"); } return prototype; }}


  两种形式的区别


当创建的原型对象数目较少而且比较固定的话,采取简单形式。在这种情况下,原型对象的引用可以由客户端自己保存。


当创建的原型对象数目不固定的话,采取登记形式。在这种情况下,客户端不保存对原型对象的引用,这个任务被交给管理员对象。在复制一个原型对象之前,客户端可以查看管理员对象是否已经有一个满足要求的原型对象。如果有,可以直接从管理员类取得这个对象引用;反之,客户端就需要自行复制此原型对象。


浅拷贝与深拷贝

原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype。Prototype类需要具备以下两个条件:


1)实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出 CloneNotSupportedException异常。


2)重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。


注:Object类的clone方法只会拷贝java中的8中基本类型以及他们的封装类型,另外还有String类型。对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。


  浅拷贝(shallow copy)


被复制对象的所有变量都含有与原来的对象相同的值(仅对于简单的值类型数据),而所有的对其他对象的引用都仍然指向原来的对象。换言之,只负责克隆按值传递的数据(比如:基本数据类型、String类型)。


实体类,具体代码如下:

package com.yoodb;
public class Prototype implements Cloneable{ private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override protected Object clone() { // TODO Auto-generated method stub try { return super.clone(); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; }

}


测试函数,具体代码如下:

package com.yoodb;
public class TestMain { public static void main(String[] args) { Prototype pro = new Prototype(); pro.setName("欢迎收藏www.yoodb.com"); Prototype prot = (Prototype) pro.clone(); prot.setName("欢迎收藏www.yoodb.com"); System.out.println("original object:" + pro.getName()); System.out.println("cloned object:" + prot.getName()); }
}


运行结果如下:

original object:欢迎收藏www.yoodb.comcloned object:欢迎收藏www.yoodb.com


  深拷贝 (deep copy)


被复制对象的所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,除了浅度克隆要克隆的值外,还负责克隆引用类型的数据,基本上就是被克隆实例所有的属性的数据都会被克隆出来。


实例一

实体类,具体代码如下:

package com.yoodb;
public class Prototype implements Cloneable{ private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override protected Object clone() { // TODO Auto-generated method stub try { return super.clone(); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; }

}
//实体二package com.yoodb;
public class NewPrototype implements Cloneable{ private String id;
private Prototype prototype;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public Prototype getPrototype() { return prototype; }
public void setPrototype(Prototype prototype) { this.prototype = prototype; }
@Override protected Object clone() { NewPrototype prot = null; try { prot = (NewPrototype) super.clone(); prot.prototype = (Prototype) this.getPrototype().clone(); return prot; } catch (Exception e) { // TODO: handle exception } return null; }

}


测试函数,具体代码如下:

package com.yoodb;
public class TestMain { public static void main(String[] args) { //普通赋值 Prototype pro = new Prototype(); pro.setName("欢迎收藏blog.yoodb.com"); NewPrototype newPro = new NewPrototype(); newPro.setId("yoodb"); newPro.setPrototype(pro); //克隆赋值 NewPrototype proc = (NewPrototype) newPro.clone(); proc.setId("yoodb"); proc.getPrototype().setName("欢迎收藏blog.yoodb.com");
System.out.println("original object id:" + newPro.getId()); System.out.println("original object name:" + newPro.getPrototype().getName());
System.out.println("cloned object id:" + proc.getId()); System.out.println("cloned object name:" + proc.getPrototype().getName()); }
}


运行结果如下:

original object id:yoodboriginal object name:欢迎收藏blog.yoodb.comcloned object id:yoodbcloned object name:欢迎收藏blog.yoodb.com


实例二

利用串行化来实现深克隆,把对象写道流里的过程是串行化(Serilization)过程;把对象从流中读出来是并行化(Deserialization)过程。


实体类,具体代码如下:

package com.yoodb;
import java.io.Serializable;
public class Prototype implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
//实体二package com.yoodb;
import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;
public class NewPrototype implements Serializable{
private static final long serialVersionUID = 1L;
private String id;
private Prototype prototype;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public Prototype getPrototype() { return prototype; }
public void setPrototype(Prototype prototype) { this.prototype = prototype; }
protected Object deepClone(){ try { ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bo); oo.writeObject(this);
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi = new ObjectInputStream(bi); return oi.readObject(); } catch (Exception e) { // TODO: handle exception } return null; }
}


测试函数,具体代码如下:

package com.yoodb;
public class TestMain { public static void main(String[] args) { //普通赋值 Prototype pro = new Prototype(); pro.setName("欢迎收藏www.yoodb.com"); NewPrototype newPro = new NewPrototype(); newPro.setId("yoodb"); newPro.setPrototype(pro); //克隆赋值 NewPrototype proc = (NewPrototype) newPro.deepClone(); proc.setId("yoodb"); proc.getPrototype().setName("欢迎收藏www.yoodb.com");
System.out.println("original object id:" + newPro.getId()); System.out.println("original object name:" + newPro.getPrototype().getName());
System.out.println("cloned object id:" + proc.getId()); System.out.println("cloned object name:" + proc.getPrototype().getName()); }
}


运行结果如下:

original object id:yoodboriginal object name:欢迎收藏www.yoodb.comcloned object id:yoodbcloned object name:欢迎收藏www.yoodb.com


  克隆满足的条件


clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。一般而言,clone()方法满足以下的描述:


1)对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。


2)对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型一样。


3)如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。

原型模式使用场景

1)资源优化场景。


2)类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。


3)性能和安全要求的场景。


4)通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。


5)一个对象多个修改者的场景。


6)一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。


7)在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。


注:原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。


原型模式优缺点

原型模式的优点是允许在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,然而运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。


原型模式的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。


推荐阅读

Java设计模式—工厂模式(factory pattern)

Java设计模式—单例模式(singleton pattern)

Java设计模式—建造者模式(builder pattern)

程序员未来的出路究竟在哪里?一位老码农的心声

浅谈Java后端开发工程师腾讯面试经历分享总结

程序员未来的出路究竟在哪里?一位老码农的心声

微框架Spring Boot使用Redis如何实现Session共享


更多推荐↓↓↓
 

关注下方微信公众号“Java精选”(w_z90110),回复关键词领取资料:如Mysql,Hadoop,Dubbo,CAS源码等,免费领取视频教程、资料文档和项目源码。


Java精选专注程序员推送一些Java开发知识,包括基础知识、各大流行框架(Mybatis、Spring、Spring Boot等)、大数据技术(Storm、Hadoop、MapReduce、Spark等)、数据库(Mysql、Oracle、NoSQL等)、算法与数据结构、面试专题、面试技巧经验、职业规划以及优质开源项目等。其中一部分由小编总结整理,另一部分来源于网络上优质资源,希望对大家的学习和工作有所帮助。

我就知道你“在看”!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存