原型模式是创建型模式,主要用来对原始对象进行拷贝。
1、定义
用原型实例指定创建对象的种类,并通过复制这些原型来创建新的对象。
2、使用场景
- 类初始化需要消耗非常多的资源,资源包括数据、硬件资源等,通过原型复制避免资源消耗。
- 通过 new 生成对象需要非常繁琐的数据准备或访问权限。
- 一个对象需要提供给其他对象访问,但是其他对象可能对其修改时。可以使用原型模式复制多个对象供调用者使用。即保护性拷贝。
具体实现时,使用 new 还是 Cloneable的 clone() 要根据实际情况来定:当通过 new 构造对象较为耗时或者成本较高时,通过 clone 方法才能获得效率上的提升。
3、UML图
- Prototype:抽象类或接口,申明一个克隆自身的接口。
- ConcretePrototype :具体的原型类
- Client:调用者
4、示例代码
1. 通过自定义接口实现
/**
* 申明一个克隆自身的接口
*/
public interface Prototype {
public Prototype clone();
}
public class ConcretePrototype implements Prototype {
//真正实现克隆自身的方法
@Override
public Prototype clone() {
ConcretePrototype prototype = new ConcretePrototype();
return prototype;
}
}
public class Client {
Prototype prototype;
public Client(Prototype p) {
prototype = p;
}
public void operation() {
//克隆一个newPrototype
Prototype newPrototype = prototype.clone();
}
}
2. 通过实现java的 Cloneable 接口实现
这种方式只需要实现 Cloneable 接口,重写 clone() 就可以了。
public class ConcretePrototype implements Cloneable {
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
return (ConcretePrototype) super.clone();
}
}
5、浅拷贝和深拷贝
public class ConcretePrototype implements Cloneable {
private String str;
private List<String> list = new ArrayList<String>();
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public List<String> getList() {
return list;
}
public void addString(String str) {
this.list.add(str);
}
@Override
protected ConcretePrototype clone() throws CloneNotSupportedException {
ConcretePrototype p = (ConcretePrototype) super.clone();
p.str = this.str;
p.list = this.list;
return p;
}
}
对于以上的这个实现,其实是是一个浅拷贝(影子拷贝),并没有将原始对象的所有字段都重新构造一份,而是直接指向了原始对象字段的引用,也就是拷贝对象的字段引用了原始对象的字段。它们指向的是同一个对象。在修改拷贝对象的list时,会对原始对象的list造成影响。那该如何写才不会造成影响呢?
答案就是采用深拷贝的写法,在拷贝对象时,对于引用型的字段也要采用拷贝的形式,而不是单纯的引用形式。clone( ) 修改如下:
@Override
protected ConcretePrototype2 clone() throws CloneNotSupportedException {
ConcretePrototype2 p = (ConcretePrototype2) super.clone();
p.str = this.str;
//p.list = this.list;
//对于list对象,也调用 clone() 进行拷贝
p.list = (ArrayList<String>) this.list.clone();
return p;
}
原型模式的核心问题就是对原始对象进行拷贝,拷贝时要注意深、浅拷贝问题。
在开发过程中,为减少错误,应尽量使用深拷贝,避免对原始对象造成影响。
6、Android源码中实现
Android 中 Intent 类就实现了原型模式。
public class Intent implements Parcelable, Cloneable {
/**
* 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);
}
}
@Override
public Object clone() {
return new Intent(this);
}
//省略其他代码
}