使程序運行更高效——原型模式

1.原型模式介紹

原型模式是一個創建型的模式。原型二字表明瞭該模式應該有一個樣板實例,用戶從這個樣板對象中複製出一個內部屬性一致的對象,這個過程也就是我們俗稱的“克隆”。被複制的實例就是我們所稱的“原型”,這個原型是可定製的。原型模式多用於創建複雜的或者者構造耗時的實例,因爲這種情況下,複製一個已經存在的實例可使程序運行更高效。

2,原型模式的定義

用原型實例制定創建對象的種類,並通過拷貝這些原型創建新的對象。

3.原型模式的使用場景

  1. 類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等,通過原型拷貝避免這些消耗。
  2. 通過new產生一個對象需要非常繁瑣的數據準備或訪問權限,這時可以使用原型模式。
  3. 一個對象需要提供給其它對象訪問,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用,即保護性拷貝。
    需要注意的是,通過實現Cloneable接口的原型模式在調用clone函數構造實例時並不一定比通過new操作快,只有當通過new構造對象較爲耗時或者說成本較高時,通過clone方法才能夠獲得效率上的提升。因此,在使用Cloneable時需要考慮構建對象的成本以及做一些效率上的測試。

4,原型模式的UML類圖

在這裏插入圖片描述
角色介紹:

  • Client: 客戶端用戶;
  • Prototype:抽象類或者接口,聲明具備clone能力;
  • ConcretePrototype:具體的原型類。

5.原型模式的簡單實現

下面以簡單文檔拷貝爲例來演示一下簡單的原型模式,我們在這個例子中首先創建了一個文檔對象,即WordDocument,這個文檔中含有文字和圖片。用戶經過了長時間的內容編輯後,打算對該文檔做進一步編輯,但是,這個編輯後的文檔是否會被採用還不確定,因此,爲了安全起見,用戶需要將當前文檔拷貝一份,然後再在文檔副本上進行修改,這樣,這個原始文檔就是我們上述所說的樣板實例,也就是將要被“克隆”的對象,我們稱爲原型:

/**
*文檔類型,扮演的是ConcretePrototype角色,而cloneable是代表*prototype角色
*/
public class WordDocument implements Cloneable {
	// 文本
	private String mText;
	// 圖片名稱列表
	private ArrayList<String> mImages = new ArrayList<String>();

	public WordDocument() {
		System.out.println("----------WordDocument構造函數-----------");
	}

	@Override
	protected WordDocument clone() {
		try {
			WordDocument doc = (WordDocument) super.clone();
			doc.mText = this.mText;
			doc.mImages = this.mImages;
			return doc;
		} catch (Exception e) {
		}
		return null;
	}

	public String getText() {
		return mText;
	}
	public void setText(String mText){
		this.mText=mText;
	}
	public List<String> getImages(){
		return mImages;
	}
	public void addImage(String img){
		this.mImages.add(img);
	}
	/**
	 * 打印文檔內容
	 */
	public void showDocument(){
		System.out.println("----------Word Content Start----------");
		System.out.println("Text:"+mText);
		System.out.println("Image List:");
		for (String imgName:mImages) {
			System.out.println("image name:"+imgName);
		}
		System.out.println("----------Word Content End------------");
	}
}

通過WordDocument類模擬Word文檔中的基本元素,即文字和圖片。WordDocument在該原型模式示例中扮演的角色爲ConcretePrototype,而Cloneable的角色則爲Prototype。WordDocument中的clone方法用以實現對象克隆。注意,這個方法並不是Cloneable接口中的,而是Object中的方法。Cloneable也是一個標識接口,它表示這個類的對象是拷貝的。如果沒有實現Cloneable接口卻調用了clone()函數將拋出異常。在這個示例中,我們通過實現Cloneable接口和覆寫clone方法實現原型模式。
Client類:

 public class Client {
	public static void main(String[] args) {
		// 1.構建文檔對象
		WordDocument originDoc = new WordDocument();
		// 2.編輯文檔,添加圖片等
		originDoc.setText("這是一篇文檔");
		originDoc.addImage("圖片1");
		originDoc.addImage("圖片2");
		originDoc.addImage("圖片3");
		originDoc.showDocument();

		// 以原始文檔爲原型,拷貝一份副本
		WordDocument doc2 = originDoc.clone();
		doc2.showDocument();

		// 修改文檔副本,不會影響原始文檔
		doc2.setText("這是修改過的Doc2文本");
		doc2.showDocument();

		originDoc.showDocument();
	}
}

輸出:

----------WordDocument構造函數-----------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
----------Word Content End------------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
----------Word Content End------------
----------Word Content Start----------
Text:這是修改過的Doc2文本
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
----------Word Content End------------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
----------Word Content End------------

從輸出可看出,doc2是通過originDoc.clone()創建的,並且doc2第一次輸出的時候和originDoc輸出是一樣,即doc2是originDoc的一份拷貝,它們的內容是一樣的,而doc2修改了文本內容以後並不影響originDoc的文本內容,這就保證了originDoc的安全性。還需要注意的是,通過clone拷貝對象時並不會執行構造函數。因此,如果在構造函數中需要一些特殊的初始化操作的類型,在使用Cloneable實現拷貝時,需要注意構造函數不會執行的問題。

6.淺拷貝和深拷貝

上述原型模式的實現實際上只是一個淺拷貝,也稱影子拷貝,這份拷貝實際上並不是將原始文檔的所有字段都重新構造一份,而是 副本文檔的字段引用原始文檔的字段,如圖:

我們知道A引用B就是說兩個對象指向同一個地址,當修改A時B也會改變,B修改時A同樣會改變。我們直接看下面的例子,將main函數的內容修改爲如下:

public class Client {
	public static void main(String[] args) {
		// 1.構建文檔對象
		WordDocument originDoc = new WordDocument();
		// 2.編輯文檔,添加圖片等
		originDoc.setText("這是一篇文檔");
		originDoc.addImage("圖片1");
		originDoc.addImage("圖片2");
		originDoc.addImage("圖片3");
		originDoc.showDocument();

		// 以原始文檔爲原型,拷貝一份副本
		WordDocument doc2 = originDoc.clone();
		doc2.showDocument();

		// 修改文檔副本,不會影響原始文檔
		// doc2.setText("這是修改過的Doc2文本");
		// doc2.showDocument();

		doc2.addImage("哈哈哈.jpg");
		doc2.showDocument();

		originDoc.showDocument();
	}
}

輸出結果:

----------WordDocument構造函數-----------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
----------Word Content End------------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
----------Word Content End------------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
image name:哈哈哈.jpg
----------Word Content End------------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
image name:哈哈哈.jpg
----------Word Content End------------

最後兩份文檔信息輸出是一致的。我們在doc2添加了一張名爲“哈哈哈.jpg”的圖片,但是,同時也顯示在originDoc中了,這是因爲上文中WordDocument的clone方法中只是簡單地進行淺拷貝,引用類型的新對象doc2的mImages只是單純地指向了this.mIages引用,並沒重新構造一個mImages對象,然後將原文檔中的圖片添加到新的mImages對象中,這樣就導致doc2中的mImages與原始文檔中的是同一個對象,因此,修改了其中一個文檔中的圖片,另一個文檔也會受影響。doc2的mImages添加了新的圖片,實際上也就是往originDoc裏添加了新圖片,所以,originDoc裏面也有“哈哈哈.jpg”圖片文件。那如何解決這個問題呢?答案就是採用深拷貝,即 在拷貝對象時,對於引用型的字段也要採用拷貝的形式,而不是單純的形式,而不是單純引用的形式。 clone方法修改如下:

@Override
	protected WordDocument clone() {
		try {
			WordDocument doc = (WordDocument) super.clone();
			doc.mText = this.mText;
			// 淺拷貝
			// doc.mImages = this.mImages;
			// 深拷貝
			doc.mImages = (ArrayList<String>) this.mImages.clone();
			return doc;
		} catch (Exception e) {
		}
		return null;
	}

如上述代碼所示,將doc.mImages指向this.mImages的一份拷貝,而不是this.mImages本身, 這樣在doc2添加圖片時並不會影響originDoc,運行效果如下:

----------WordDocument構造函數-----------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
----------Word Content End------------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
----------Word Content End------------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
image name:哈哈哈.jpg
----------Word Content End------------
----------Word Content Start----------
Text:這是一篇文檔
Image List:
image name:圖片1
image name:圖片2
image name:圖片3
----------Word Content End------------

原型模式是非常簡單的模式,它的核心問題就是對原始對象進行拷貝,在這個模式的使用過程中需要注意的一點就是:深、淺拷貝的問題。爲了減少出錯,建議大家在使用該模式時使用深拷貝,避免操作副本時影響原始對象的問題。

————摘自《Android 源碼設計模式解析與實戰 第四章》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章