原型模式
1.定義:
用原型實例指定創建對象的種類,并通過copy這些原型創建新的對象。
2.使用場景:
- 類初始化需要消耗非常多的資源,這個資源包括數據、硬件資源等,通過原型copy避免了這些消耗;
- 通過new產生一個對象需要非常繁瑣的數據準備或訪問權限,這時可以使用原型模式;
- 一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式copy多個對象供調用者使用,即保護性copy。
注意:
1.通過實現Cloneable接口的原型模式再調用clone函數構造實例時,并不一定比通過new操作效速度快,只有當通過new構造對象較為耗時或成本較高時,通過clone方法才能獲得效率上的提升。因此,在使用Cloneable時需要考慮構建對象的成本以及一些效率上的測試。
2.實現原型模式不一定非要實現Cloneable接口,也有其他的實現方式。
3.UML圖
4.詳解:
原型模式是一個創建型模式,‘原型’二字表明該模式應該有一個樣板實例,提供給用戶訪問copy。用戶從這個樣板對象中復制出一個內部屬性一致的對象的過程,稱為克隆。被復制的樣板對象就是‘原型’。原型模式多用于創建復雜的或構造耗時的實例,因為這些情況下,復制一個已經存在的實例可使程序運行效率更高。
下面用代碼舉例闡述:
public static class WordDocument implements Cloneable {
private String text;
private ArrayList<String> images = new ArrayList<>();
public WordDocument() {
System.out.println("constructor start");
}
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.text = text;
doc.images = images;//淺拷貝
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public List<String> getImages() {
return images;
}
public void addImage(String img) {
images.add(img);
}
public void showDocument() {
System.out.println("text:" + text);
System.out.print("images:");
for (int i = 0; i < images.size(); i++) {
if (i == images.size() - 1) {
System.out.print(images.get(i) + "\n");
} else {
System.out.print(images.get(i) + ",");
}
}
}
}
測試代碼:
public static void main(String[] args) {
WordDocument originDoc = new WordDocument();
originDoc.setText("原文檔text");
originDoc.addImage("img1");
originDoc.addImage("img2");
originDoc.addImage("img3");
originDoc.showDocument();
System.out.println("=========================");
WordDocument newDoc = originDoc.clone();
newDoc.showDocument();
System.out.println("=========================");
newDoc.setText("修改后的text");
newDoc.addImage("img4");
newDoc.showDocument();
System.out.println("=========================");
originDoc.showDocument();
/**
* 輸出結果:
constructor start
text:原文檔text
images:img1,img2,img3
=========================
text:原文檔text
images:img1,img2,img3
=========================
text:修改后的text
images:img1,img2,img3,img4
=========================
text:原文檔text
images:img1,img2,img3,img4
*/
}
這里的WordDocument 就是所謂的‘原型’,它里面有文字描述與圖片url。現在用戶需要重新編輯該文檔,又想不破壞原文檔,于是用戶需要復制一份原文檔,在拷貝文檔上編輯,詳細過程見測試代碼。
從上面的測試結果可以得出如下結論:
- 通過clone 拷貝對象時,并不會執行構造函數,因此,如果在構造函數中需要初始化操作的類型,在使用Cloneable實現拷貝時,需注意構造函數不會執行的問題。
- 副文檔修改了文本內容,原文檔內容不受影響,這樣保證了原文檔的安全性;
- 但是,細心的你也許發現了,在副文檔中添加img4,原文檔也會有img4。說好的安全呢?瞬間被打臉,有木有?其實這個例子是淺拷貝(也稱為‘影子拷貝’),這份拷貝并不是將原始文檔的所有字段都重構了一份,而是副文檔的字段引用了原始文檔的字段,即原文檔與副文檔的字段images其實指向同一個地址,于是才會出現上述情況。那么這種情況該如何規避?答案是深拷貝!即在拷貝對象時,對于引用型的字段也采用拷貝的形式,而不是上面的單純引用的方式。
下面修改clone方法:doc.images = (ArrayList<String>) images.clone(),即深拷貝。
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.text = text;
doc.images = (ArrayList<String>) images.clone();//深拷貝
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
測試代碼不變,但是輸出的結果卻變了,如下:
/**
* 輸出結果:
constructor start
text:原文檔text
images:img1,img2,img3
=========================
text:原文檔text
images:img1,img2,img3
=========================
text:修改后的text
images:img1,img2,img3,img4
=========================
text:原文檔text
images:img1,img2,img3
*/
修改了副文檔的images,原文檔也沒有改變,很安全了。
原型模式是非常簡單的模式,它的核心問題就是對原始對象進行拷貝,在這個模式的使用過程中需要注意的一點就是深、淺拷貝的問題。在實際開發過程中,為了減少錯誤,盡量使用深拷貝原型模式,避免操作副本對象時,影響了原始對象的屬性