原型模式(Prototype Pattern)定义如下:Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype. 即:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式的通用类图如下所示:
原型模式的核心就是一个clone方法,通过该方法进行对象的拷贝。Java提供了一个Cloneable接口来标示这个对象是可拷贝的,这个接口只是一个标记作用,在JVM中具有这个标记的对象才有可能被拷贝。
原型模式通用代码:
public class Prototype implements Cloneable{
@Override
public Prototype clone(){
//覆写Object的clone方法
Prototype prototype = null;
try{
prototype = (Prototype)super.clone();
}catch (Exception e) {
//异常处理
}
return prototype;
}
}
原型模式的应用
原型模式的优点
- 性能优良。原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
- 避免构造函数的约束。直接在内存中拷贝,构造函数不会执行。
原型模型的使用场景
- 资源优化场景初始化需要消耗非常多的资源。
- 一个对象有多个修改者。一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其内容,可以考虑使用原型模型。
- 性能和安全要求场景。通过new产生一个对象需要非常繁琐的数据准备等。
原型模式的注意事项
- 构造函数不会被执行。一个实现了Cloneable接口并重写了clone方法的类A,有一个无参数或有参数构造方法B,通过new关键字产生一个对象S后,再通过S.clone()方式产生新对象T,那么对象拷贝时构造函数B是不会被调用的。可以参考下面的Demo。
public class P implements Cloneable{
public P(){
System.out.println("call P's constructor...");
}
@Override
public P clone(){
P p = null;
try {
p = (P)super.clone();
} catch (Exception e) {
System.out.println("Exception:" + e.getMessage());
}
return p;
}
public void doSomething(){
System.out.println("Do something...");
}
}
public class Demo {
public static void main(String[] args) {
P p1 = new P();
p1.doSomething();
P p2 = p1.clone();
p2.doSomething();
}
}
- 浅拷贝和深拷贝
我们可以先来看两个代码和运行结果对比的例子。
代码:
public class Thing1 implements Cloneable{
private ArrayList<String> list = new ArrayList<String>();
public int num = 0;
@Override
public Thing1 clone(){
Thing1 thing = null;
try {
thing = (Thing1)super.clone();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return thing;
}
@Override
public String toString() {
return "Thing1 [list=" + list + ", num=" + num + "]";
}
public void setList(String s){
this.list.add(s);
}
}
public class Thing2 implements Cloneable{
private ArrayList<String> list = new ArrayList<String>();
public int num = 0;
@Override
public Thing2 clone(){
Thing2 thing = null;
try {
thing = (Thing2)super.clone();
this.list = (ArrayList<String>)this.list.clone();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return thing;
}
@Override
public String toString() {
return "Thing2 [list=" + list + ", num=" + num + "]";
}
public void setList(String s){
this.list.add(s);
}
}
public class Client {
public static void main(String[] args) {
System.out.println("\n========浅拷贝============\n");
Thing1 p = new Thing1();
Thing1 p1 = p.clone();
System.out.println(p.toString());
System.out.println(p1.toString());
System.out.println("--------------------");
p.setList("quan");
p.num = 1;
p1.setList("master");
p1.num = 2;
System.out.println(p.toString());
System.out.println(p1.toString());
System.out.println("\n========深拷贝============\n");
Thing2 q = new Thing2();
Thing2 q1 = q.clone();
System.out.println(q.toString());
System.out.println(q1.toString());
System.out.println("--------------------");
q.setList("QUAN");
q.num = 1;
q1.setList("MASTER");
q1.num = 2;
System.out.println(q.toString());
System.out.println(q1.toString());
}
}
运行结果:
========浅拷贝============
Thing1 [list=[], num=0]
Thing1 [list=[], num=0]
--------------------
Thing1 [list=[quan, master], num=1]
Thing1 [list=[quan, master], num=2]
========深拷贝============
Thing2 [list=[], num=0]
Thing2 [list=[], num=0]
--------------------
Thing2 [list=[QUAN], num=1]
Thing2 [list=[MASTER], num=2]
通过对比我们可以看出,这是两种不同形式的拷贝方式。为什么会有这样的结果呢?出现这种结果的根本原因是:Java在clone的时候偷了个懒,Object类提供的clone()方法只拷贝本对象内部的原始类型属性,对其对象内部的引用对象只拷贝其引用,而不拷贝引用对象本身。也导致了新对象内的引用依旧指向原生对象的北部元素地址,这种拷贝方式就叫做浅拷贝。也就是说,浅拷贝的对象会共享引用对象(String除外)。由于浅拷贝存在引用变量的共享,因此是一种比较危险的拷贝方式,为了消除这种共享对象而带来的不可预估的结果,有时候我们需要使用深拷贝。深拷贝,是一种递归的拷贝方式,即深拷贝一个对象时,该对象内的引用对象也必须同时进行自我深拷贝。可以对比上述代码,深拷贝的结果是新拷贝对象内的引用对象与原始对象中的引用对象相互独立,互不影响。
- clone和final
对象的clone与对象内的final关键字是相互冲突的。如果要使用clone方法,在类的成员变量上就不要添加final关键字。
《注》以上内容总结自秦小波-《设计模式之禅》,仅为个人学习笔记。
网友评论