介紹
原型模式是一個創建型模式,該模式有一個樣板實例,用戶從這個對象中復制出一個內部屬性一致的對象。多用于創建復雜的或者構造耗時的實例。因為在這種情況下,復制一個已經存在的實例可使程序運行更加高效。
定義
用原型實例制定創建對象的種類,并通過拷貝這些原型創建新的對象。
使用的場景
- 類初始化需要消耗非常多的資源,包括數據、硬件資源等,通過原型拷貝避免一些消耗。
- 通過new產生一個對象需要非常繁瑣的數據或訪問權限。
- 一個對象需要提供給其他對象訪問,而且各個調用者可能需要修改其值時,可以考慮用原型模式拷貝多個對象供調用者使用,即保護性拷貝。
簡單實現
現有文檔對象,包含文字和圖片列表?,F在用戶需要對文檔進行編輯,但是無法確定編輯后的文檔是否采用,此時可以將原始文檔拷貝一份,然后在副本上進行修改。
/**
* Created by Bowen on 2016-04-23.
*/
public class Doc implements Cloneable {
private String mText;
private ArrayList<String> mImages = new ArrayList<>();
public Doc() {
}
public Doc(String mText, ArrayList<String> mImages) {
this.mText = mText;
this.mImages = mImages;
}
public void addImage(String image){
mImages.add(image);
}
public String getmText() {
return mText;
}
public void setmText(String mText) {
this.mText = mText;
}
public ArrayList<String> getmImages() {
return mImages;
}
public void setmImages(ArrayList<String> mImages) {
this.mImages = mImages;
}
public void show(){
System.out.println("Text的內容: "+mText);
int size = mImages.size();
for (int i = 0; i < size; i++) {
System.out.println("Image的內容: "+mImages.get(i));
}
System.out.println("==========================");
}
@Override
protected Object clone() throws CloneNotSupportedException {
Doc doc = (Doc) super.clone();
doc.mImages = mImages;
doc.mText = this.mText;
return doc;
}
}
我們來看客戶端使用:
/**
* Created by Bowen on 2016-04-23.
*/
public class Test {
public static void main(String[] args){
Doc originDoc = new Doc();
originDoc.addImage("test1");
originDoc.setmText("文字1");
originDoc.show();
try {
Doc newDoc = (Doc) originDoc.clone();
newDoc.setmText("文字2");
newDoc.show();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
originDoc.show();
}
}
以下是輸出結果:
Text的內容: 文字1
Image的內容: test1
==========================
Text的內容: 文字2
Image的內容: test1
==========================
Text的內容: 文字1
Image的內容: test1
==========================
可以看到newDoc克隆后修改了Text字段,能正確的顯示,而且并沒有影響originDoc的文本內容。
深拷貝 淺拷貝
上述的原型模式知識一個淺拷貝,也成為影子拷貝。實際上,newDoc并不是將原始文檔的所有字段都重新構造了一份,而是引用了originDoc的字段。
A引用B,說明AB兩個對象指向同一個地址,當修改A時,B也會變,修改B同理。修改客戶端代碼:
/**
* Created by Bowen on 2016-04-23.
*/
public class Test {
public static void main(String[] args){
Doc originDoc = new Doc();
originDoc.addImage("test1");
originDoc.setmText("文字1");
originDoc.show();
try {
Doc newDoc = (Doc) originDoc.clone();
newDoc.setmText("文字2");
newDoc.addImage("test2");
newDoc.show();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
originDoc.show();
}
}
在newDoc中添加衣服圖片。結果顯示:
Text的內容: 文字1
Image的內容: test1
==========================
Text的內容: 文字2
Image的內容: test1
Image的內容: test2
==========================
Text的內容: 文字1
Image的內容: test1
Image的內容: test2
==========================
可以看到,我們在newDoc中添加的圖片,同樣出現在了originDoc中。這是因為上文Doc中clone方法知識簡單的進行淺拷貝,引用類型的新對象newDoc的mImages只是指向了originDoc.mImages引用,并沒有重新構造一個Images對象,所以當我們在newDoc中添加圖片時,導致了originDoc的mImages與originDoc中的是同一個對象,因此,修改任意一個,另一個文檔也會受到影響。
解決問題就是采用深拷貝,再拷貝對象時,對于引用型的字段也要采用拷貝的形式,而不是單純的引用形式,修改clone方法:
@Override
protected Object clone() throws CloneNotSupportedException {
Doc doc = (Doc) super.clone();
doc.mImages = (ArrayList<String>) this.mImages.clone();
doc.mText = this.mText;
return doc;
}
再次運行,結果如下:
Text的內容: 文字1
Image的內容: test1
==========================
Text的內容: 文字2
Image的內容: test1
Image的內容: test2
==========================
Text的內容: 文字1
Image的內容: test1
==========================
滿足預期結果。
總結
原型模式本質上是對象拷貝,與C++中的拷貝構造函數有些類似。使用原型模式可以
- 解決構造復雜對象的資源消耗問題,能夠在某些場景提升創建對象的效率。
- 保護性拷貝,是某個對象對外是只讀的,通常可以通過返回一個對象拷貝的形式實現只讀的限制。
- 優點
- 原型模式是在內存中二進制流的拷貝,比直接new一個對象性能好很多,特別是要在循環體內產生大量對象時,原始模式可以更好的體現其優點。
- 缺點
- 在內存中拷貝,構造函數時不會執行的。