原型模式是一种创建型设计模式,通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。
原型模式要求对象实现一个可以“克隆”自身的接口,该接口通过复制一个实例对象本身来创建一个新的实例。那么原型实例创建新的对象时就无需关心这个实例本身的类型,只要实现克隆自身的方法,就可以通过这个方法来获取新的对象,没必要再去通过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 读取二进制流。
原型模式优缺点
—
原型模式的优点是允许在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,然而运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。
原型模式的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。