《大话设计模式》书中描述原型(Prototype)模式:
原型模式(Prototype):用用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式(Prototype Pattern)结构图
这种形式涉及到三个角色:
- 客户(Client)角色:客户类提出创建对象的请求。
- 抽象原型(Prototype)角色:这是一个抽象角色,通常由Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。
- 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口。
源代码:
抽象原型角色
public interface Prototype{ /* 克隆自身的方法 @return 一个从自身克隆出来的对象 */ public Object clone(); }
具体原型角色
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; } }
客户端角色
public class Client { /* 持有需要使用的原型接口对象 */ private Prototype prototype; /* 构造方法,传入需要使用的原型接口对象 */ public Client(Prototype prototype){ this.prototype = prototype; } public void operation(Prototype example){ //需要创建原型接口的对象 Prototype copyPrototype = prototype.clone(); } }
Java中的克隆方法
Java的所有类都是从java.lang.object类继承而来的,而Object类提供protected Object clone()方法对对象进行复制,子类当然也可以把这个方法置换掉,提供满足自己需要的复制方法。对象的复制有一个基本问题,就是对象通常都有对其他的对象的引用。当使用Object类的clone()方法来复制一个对象时,此对象对其他对象的引用也同时会被复制一份。
Java语言提供的Cloneable接口只起一个作用,就是在运行时期通知Java虚拟机可以安全地在这个类上使用clone()方法。通过调用这个clone()方法可以得到一个对象的复制。由于Object类本身并不实现Cloneable接口,因此如果所考虑的类没有实现Cloneable接口时,调用clone()方法会抛出CloneNotSupportedException异常。
克隆满足的条件
clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。一般而言,clone()方法满足一下的描述:
- 对任何的对象x,都有:x.clone()!=x。换而言之,克隆对象与原对象不是同一个对象。
- 对任何的对象x,都有:x.clone().getClass() == x.getClass(),换而言之,克隆对象与原对象的类型一样。
- 如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。
在Java语言的API中,凡是提供了clone()方法的类,都满足上面的这些条件。Java语言的设计师在设计自己的clone()方法时,也应当遵守三个条件。一般来说,上面的三个条件中的前两个时必需的,而第三个是可选的。
浅克隆和深克隆
无论你是自己实现克隆方法,还是采用Java提供的克隆方法,都存在一个浅度克隆和深度克隆的问题。
- 浅度克隆:只负责克隆按值传递的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换而言之,被复制对象的所有变量都含有与原来的对象相同值,而所有的对其他对象的引用都仍然指向原来的对象。
- 深度克隆:除了浅度克隆的值外,还负责克隆引用类型的数据。那些引用其他的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换而言之,深度克隆把复制的对象都复制了一遍,而这种对引用到的对象的复制叫做间接复制。
利用序列化实现深度克隆
把对象写到流里的过程是序列化(Serialization)过程;而把对象从流中读出来的过程则叫反序列化(Deserialization)过程。应该指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
在Java语言里深度克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。
public Object deepClone() throws IOException, ClassNotFoundException{ //将对象写到流里 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); //从流里读回来 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); }
这样做的前提就是对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在复制过程之外。
浅度克隆显然比深度克隆更容易实现,因为Java语言的所有类都会继承一个clone()方法,而这个clone()方法所做的正式浅度克隆。有一些对象,比如线程(Thread)对象或Socket对象,是不能简单复制或共享的。不管是使用浅度克隆还是深度克隆,只要涉及这样的间接对象设成transient而不予以复制;或者由程序自行创建出相当的同种对象,权且当做复制件使用。
参考资料:
http://www.cnblogs.com/java-my-life/archive/2012/04/11/2439387.html