原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。例如:有一张个人简历(此为具体原型),需要在这个基础上复印十份简历。如果不复印,那就得手写10份简历,耗时耗力。这就是原型模式所表现出来的优势。具体代码如下:
1 /** 2 * 抽象原型角色,这是一个抽象角色,通常由一个JAVA接口或JAVA抽象类实现。 3 * 此角色给出所有的具体原型类所需要的接口 4 */ 5 public interface IPrototype { 6 IPrototype clone(); 7 String getId(); 8 void setId(String id); 9 }
1 /** 2 * 具体原型角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口 3 */ 4 public class ConcretePrototype1 implements IPrototype { 5 private String id; 6 @Override 7 public IPrototype clone() { 8 ConcretePrototype1 prototype=new ConcretePrototype1(); 9 prototype.setId(this.id); 10 return prototype; 11 } 12 13 @Override 14 public String getId() { 15 return id; 16 } 17 18 @Override 19 public void setId(String id) { 20 this.id=id; 21 } 22 }
1 /** 2 * 客户(Client)角色:客户类提出创建对象的请求。 3 */ 4 public class Client { 5 public static void main(String[] args) { 6 IPrototype p1=new ConcretePrototype1(); 7 p1.setId("9527"); 8 System.out.println(p1.getId()); 9 IPrototype c1=p1.clone(); 10 System.out.println(c1.getId()); 11 IPrototype c2=p1.clone(); 12 System.out.println(c2.getId()); 13 System.out.println(p1==c1); 14 System.out.println(c1==c2); 15 } 16 }
注意:为什么要用IPrototype c1=p1.clone();而不是IPrototype c1=p1呢?如果这样做的话,模拟到现实的简历就相当于在另外10份简历上没有你的个人信息,而是相当于写一串文字“我的简历在我的手上,请联系我”,哈哈.....
浅克隆和深克隆
无论你是自己实现克隆方法,还是采用Java提供的克隆方法,都存在一个浅度克隆和深度克隆的问题。
1、浅克隆
只负责克隆按值传递的数据(比如基本数据类型、String类型),而不是复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。
2、深克隆
除了浅克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。
利用序列化实现深度克隆
把对象写到流里的过程是序列化(Serialization)过程;而把对象从流中读出来的过程则叫反序列化(Deserialization)过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
在Java语言里深度克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。
这样做的前提就是对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在复制过程之外。
浅度克隆显然比深度克隆更容易实现,因为Java语言的所有类都会继承一个clone()方法,而这个clone()方法所做的正式浅度克隆。
有一些对象,比如线程(Thread)对象或Socket对象,是不能简单复制或共享的。不管是使用浅度克隆还是深度克隆,只要涉及这样的间接对象,就必须把间接对象设成transient而不予复制;或者由程序自行创建出相当的同种对象,权且当做复制件使用。
1 public class DeepCopy implements Serializable { 2 int i; 3 /** 4 * 深度复制,实参类必须实现Serializable接口 5 * @param o 6 * @return 7 * @throws IOException 8 * @throws ClassNotFoundException 9 */ 10 public static Object deepCopy(Object o) throws IOException, ClassNotFoundException { 11 //序列化,写入到流 12 ByteArrayOutputStream bo=new ByteArrayOutputStream(); 13 ObjectOutputStream oo=new ObjectOutputStream(bo); 14 oo.writeObject(o); 15 //反序列化,从流里读取出来,即完成复制 16 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); 17 ObjectInputStream oi=new ObjectInputStream(bi); 18 return oi.readObject(); 19 } 20 21 public static void main(String[] args) throws IOException, ClassNotFoundException { 22 System.out.println("=========未使用深度复制========="); 23 DeepCopy dc1=new DeepCopy(); 24 dc1.i=1;//初始化dc1里的i值 25 DeepCopy dc2=dc1; 26 dc1.i=2;//改变dc1里i的值 27 System.out.println("dc1:"+dc1.i); 28 System.out.println("dc2(引用传递):"+dc2.i); 29 System.out.println("=========使用深度复制========="); 30 DeepCopy dc3 = new DeepCopy(); 31 dc3.i=1; 32 DeepCopy dc4=(DeepCopy)deepCopy(dc3); 33 dc3.i=2;//改变dc3里i的值 34 System.out.println("dc3:"+dc3.i); 35 System.out.println("dc4(引用传递):"+dc4.i); 36 } 37 }
原型模式的优点
原型模式允许在运行时动态改变具体的实现类型。原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了。因为克隆一个原型就类似于实例化一个类。
原型模式的缺点
原型模式最主要的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。