一、原型模式的核心思想
原型模式(PrototypePatterm)是创建型模式的一种,其特点在于通过"复制"一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的"原型",这个原型是可定制的。
用面向对象的方法来说就是,我们先建立一个原型,然后通过对原型进行复制和修饰的方法,就可以产生一个与原型相似的新对象。即:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
在Java中复制模型对象是通过clone0方法实现的。其实,这个方法可以是任意名字,比如cloneA0、cloneBO等。不过,一般应该使用clone0方法,这样做有两个原因:一是出于习惯,复制对象当然应该是 clone0;二是,在许多语言中的基础类中,比如作为所有类基础的Obiect,都定义了clone()方法。因此,实现原型模式的方法一般应该是原型类继承了Cloneable 接口,在具体原型类中需要实现 clone0方法,完成对象的自我复制。如下程序是一个典型的原型类:
原型类Prototype.iava
java
package creation.prototype;
/ **
* @author Minggg
* 原型模式
* /
public class Prototype implements Cloneable {
/ **
* 浅复制
* /
public Object clone() throws CloneNotSupportedException {
Prototype prototype = (Prototype) super.clone();
return prototype;
}
}
调用 Prototype 模式很简单:
java
Prototype obj = new Prototype();
Prototype obj2 =obj.clone();//复制
当然也可以结合工厂模式来创建 Prototype 实例。
二、浅复制与深复制
从以上的使用可以看出,在Java中Prototype 模式变成 clone()方法的使用,此方法执行的是该对象的"浅复制",而不"深复制"操作。
因此,复制分为两种:复制浅和深复制。
- 浅复制:将一个对象复制之后,生成一个新的对象,新对象的所有成员变量(基本类型或引用类型)都含有与原有对象相同的值,如果原有对象的成员变量是基本数据类型,就会将这个变量的值复制一份到新对象里面,如果原有对象的成员变量是引用数据类型,那么这个引用指向的对象不会新生成一份,而是,在新对象里面的这个引用与原有对象的引用指向的是同一个对象。
- 深复制:将一个对象复制之后,生成一个新的对象,新对象的基本数据类型变量含有与原有对象相同的值,如果原有对象的成员变量是引用数据类型,在新对象里面,这些引用变量将指向被复制过的新对象,而不再是指向原有的那些被引用的对象,深复制把要复制的对象所引用的对象都复制一遍。
为了实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出该二进制数据对应的对象。
为了展示浅复制和深复制在Java中的区别,我们新建一个类,其中包含了一个基本数据类型String 变量和一个引用数据类型SerializableObject的变量。
- 在执行浅复制时,String变量的值会被复制一份,而SerializableObiect变量只会引用同一个对象。
- 在执行深复制时,String变量和SerializableObiect变量的值都会被复制一份。
完整的代码如下程序所示:
深复制 DeepPrototype.java
java
package creation.prototype;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class DeepPrototype implements Cloneable, Serializable {
private static final long serialVersionUID=1L;
private String str;
private SerializableObject obj;
/**
* 浅复制
*/
public Object clone() throws CloneNotSupportedException {
DeepPrototype prototype = (DeepPrototype) super.clone();
return prototype;
}
/**
*深复制
*/
public Obiect 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();
}
public String getStr() {
return str;
}
public void setStr(String str){
this.str = str;
}
public SerializableObject getObj(){
return obj;
}
public void setObj(SerializableObject obj){
this.obj = obj;
}
class SerializableObject implements Serializable {
private static final long serialVersionUID=1L
}
}
下面我们来创建一个 DeepPrototype对象pt,并分别使用浅复制和深复制来复制一个新的对象pt1 和 pt2,并修改 String 的值,查看输出的结果有什么不同。完整的代码如下程序所示:
测试程序 PrototypeTest.java
java
package creation.prototype;
import java.io.IOException;
public class DeepPrototypeTest {
public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException {
// 创建对象
DeepPrototype pt = new DeepPrototype();
pt.setStr("Hello World!");
SerializableObject obj= new SerializableObject();
pt.setObj(obj);
// 浅复制
DeepPrototype pt1 =(DeepPrototype) pt.clone();
System.out.printIn("浅复制原对象 str:"+pt.getStr());
System.out.println("浅复制新对象 str:"+pt1.getStr());
System,out.println("浅复制原对象 obj:"+pt.getObj());
System.out.println("浅复制新对象 obj:"+pt1.getObj());
// 修改浅复制对象的值
pt1 .setStr("Hello China!");
System,out.println("修改后浅复制原对象 str:"+ pt.getStr());
System.out.println("修改后浅复制新对象 str:"+ pt1.getStr());
System,out.println("修改后浅复制原对象 obj:"+ pt.getObj()):
System.out.println("修改后浅复制新对象 obj:"+ pt1.getObj());
// 深复制
DeepPrototype pt2 = (DeepPrototype) pt.deepClone();
System.out.println("深复制原对象 str:"+pt.getStr());
System.out.println("深复制新对象 str:"+pt2.getStr());
System.out.println("深复制原对象 obj:"+pt.getObj());
System.out.println("深复制新对象 obj:"+pt2.getObj());
// 修改深复制对象的值
pt2.setStr("Hello China!");
System.out.println("修改后深复制原对象 str:"+pt.getStr());
System.out.println("修改后深复制新对象 str:"+pt2.getStr());
System.out.println("修改后深复制原对象 obj:"+pt.getObj());
System.out.println("修改后深复制新对象 obj:"+pt2.getObj());
}
}
运行该程序的结果为:
java
浅复制原对象 str:Hello World!
浅复制新对象 str:Hello World!
浅复制原对象 obj:creation.prototype.SerializableObject@lfb8ee
浅复制新对象 obj:creation.prototype.SerializableObject@lfb8ee
修改后浅复制原对象 str:Hello World!
修改后浅复制新对象 str:Hello China!
修改后浅复制原对象obj:creation,prototype,SerializableObject@1fb8ee
修改后浅复制新对象obj:creation.prototype.SerializableObject@1fb8ee3
深复制原对象 str:Hello World!
深复制新对象 str:Hello World!
深复制原对象 obj:creation.prototype.SerializableObject@1fb8ee3
深复制新对象 obj:creation.prototype.SerializableObject@ld58aac
修改后深复制原对象 str:Hello World!
修改后深复制新对象 str:Hello China!
修改后深复制原对象 obj:creation.prototype.SerializableObject@1fb8ee
修改后深复制新对象 obj:creation.prototype.SerializableObject@1d58aa
从结果可以看出,浅复制的对象对于简单对象Sting的值可以不同,但引用类型obj的值却没有改变;而深复制的str和obi的值都被复制了一份。
因此,使用Obiect.clone()方法只能浅层次的克隆,即只能对那些成员变量是基本类型或String类型的对象进行克隆,对那些成员变量是类类型的对象进行克隆要使用到对象的序列化,不然克降出来的 Prototype 对象都共享同一个 obj 实例。
总而言之,Prototype 模式为我们提供另外一种高效创建对象的方法。使用Prototype 模式,我们可以不了解原型对象的任何细节及它内部的层次结构,快速克隆出一个个同样的对象来,而且这些对象间无影响,但是我们必须要实现它特定的接口。
三、何时使用原型模式
原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。
原型模式也是一种创建型模式,它关注的是大量相似对象的创建问题。我们经常会遇到这样的情况:在系统中要创建大量的对象,这些对象之间具有几乎完全相同的功能,只是在细节上有一点儿差别。