原型模式是創建型模式,主要用來對原始對象進行拷貝。
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);
}
//省略其他代碼
}