原型模式定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
原型模式类图
类图说明
- Client:客户端用户。
- Prototype:抽象类或者接口,声明具备Clone能力。
- ConcretePrototype:具体的原型类。
原型模式的优点
- 性能优良:
- 原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
- 逃避构造函数的约束:
- 这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。因此,如果在构造函数中需要一些特殊的初始化操作的类型,在使用Cloneable实现拷贝时,需要注意构造函数不会执行的问题。
原型模式的使用场景
1、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
2、通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式。
3、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值,同时有可能每个对象中有一些共有属性时,可以考虑使用原型模式拷贝多个对象提供调用者使用,即保护性拷贝。
PS:需要注意的是,通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者说成本较高时,通过clone方法才能获得效率上的提升。因此,在使用Cloneable时需要考虑构建对象的成本以及做一些效率上的测试。当然,实现原型模式也不一定非要实现Cloneable,也有其他的实现方式。
原型模式的简单实现
// 简单的可拷贝对象 public class Thing implements Cloneable { // 构造函数,在拷贝对象时不会被调用 public Thing() { System.out.println("构造函数被执行了..."); } @Override protected Object clone() throws CloneNotSupportedException { Thing thing = null; try { thing = (Thing) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return thing; } } // 简单的场景类 public class Client { public static void main(String[] args) { // 产生一个对象 Thing thing = new Thing(); // 拷贝一个对象 Thing cloneThing = thing.clone(); } }
运行结果:
构造函数被执行了...
浅拷贝
Object类提供的方法clone只是拷贝本对象(和其中的全局原始类型,比如int、long、char等,还有String),其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址。
浅拷贝的例子:
public class Thing implements Cloneable { // 定义一个私有变量 private ArrayList<String> arrayList = new ArrayList<>(); @Override protected Object clone() throws CloneNotSupportedException { Thing thing = null; try { thing = (Thing) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return thing; } //设置arrayList的值 public void setValue(String value) { this.arrayList.add(value); } // 取得arrayList的值 public ArrayList<String> getValue() { return this.arrayList; } } public class Client { public static void main(String[] args) { // 产生一个对象 Thing thing = new Thing(); // 设置一个值 thing.setValue("张三"); // 拷贝一个对象 Thing cloneThing = thing.clone(); cloneThing.setValue("李四"); System.out.println(thing.getValue()); } }
运行结果:
[张三, 李四]因为通过clone拷贝出来的cloneThing对象中的arrayList没有被单独拷贝,和thing对象共用一份arrayList,所以改变了cloneThing对象中的arrayList也就改变了thing对象中的arrayList。
深拷贝
进行深度的完全拷贝。两个对象之间没有任何的瓜葛了。
深拷贝的例子:
public class Thing implements Cloneable { // 定义一个私有变量 private ArrayList<String> arrayList = new ArrayList<>(); @Override protected Object clone() throws CloneNotSupportedException { Thing thing = null; try { thing = (Thing) super.clone(); // 对arrayList也进行拷贝 thing.arrayList = (ArrayList<String>) this.arrayList.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return thing; } //设置arrayList的值 public void setValue(String value) { this.arrayList.add(value); } // 取得arrayList的值 public ArrayList<String> getValue() { return this.arrayList; } } public class Client { public static void main(String[] args) { // 产生一个对象 Thing thing = new Thing(); // 设置一个值 thing.setValue("张三"); // 拷贝一个对象 Thing cloneThing = thing.clone(); cloneThing.setValue("李四"); System.out.println(thing.getValue()); } }
运行结果:
[张三]因为对arrayList也进行了独立的拷贝,所以修改cloneThing对象中的arrayList并不会影响thing对象中的arrayList值。
clone与final
对象的clone与对象内的final关键字是有冲突的。比如上面的例子中,如果arrayList有final属性,在clone处,编译器会报错。
所以,要使用clone方法,类的成员变量上不要增加final关键字。
Android中的应用
Intent的clone方法,使用的拷贝构造函数的方式来实现。其内部并没有调用super.clone(),而是调用了new Intent(this)。正如上文提到的,使用clone和new需要根据构造对象的成本来决定,如果对象的构造成本比较高或者构造较为麻烦,那么使用clone()函数效率较高,否则可以使用new的形式。
@Override public Object clone() { return new Intent(this); } /** * Copy constructor. */ public Intent(Intent o) { this.mAction = o.mAction; this.mData = o.mData; this.mType = o.mType; this.mPackage = o.mPackage; this.mComponent = o.mComponent; this.mFlags = o.mFlags; this.mContentUserHint = o.mContentUserHint; if (o.mCategories != null) { this.mCategories = new ArraySet<String>(o.mCategories); } if (o.mExtras != null) { this.mExtras = new Bundle(o.mExtras); } if (o.mSourceBounds != null) { this.mSourceBounds = new Rect(o.mSourceBounds); } if (o.mSelector != null) { this.mSelector = new Intent(o.mSelector); } if (o.mClipData != null) { this.mClipData = new ClipData(o.mClipData); } }