java中23種設計模式--原型模式(Portotype)

王廷駿發表於2018-04-23

原型模式概述

原型模式可以通過一個物件例項確定建立物件的種類,並且通過拷貝建立新的例項.總得來說,原型模式實際上就是從一個物件建立另一個新的物件,使新的物件有具有原物件的特徵.
克隆模式類似於new 但是不同於new,new建立新的物件屬性採用的是預設值,克隆出的物件的屬性完全與原型物件相同,並且克隆出的新物件改變不會影響原型物件,然後在修改克隆物件的值.

java中23種設計模式--原型模式(Portotype)
在原型模式結構圖中包含如下幾個角色:

  • Prototype(抽象原型類):它是宣告克隆方法的介面,是所有具體原型類的公共父類,可以是抽象類也可以是介面,甚至還可以是具體實現類。
  • ConcretePrototype(具體原型類):它實現在抽象原型類中宣告的克隆方法,在克隆方法中返回自己的一個克隆物件。
  • Client(客戶類):讓一個原型物件克隆自身從而建立一個新的物件,在客戶類中只需要直接例項化或通過工廠方法等方式建立一個原型物件,再通過呼叫該物件的克隆方法即可得到多個相同的物件。由於客戶類針對抽象原型類Prototype程式設計,因此使用者可以根據需要選擇具體原型類,系統具有較好的可擴充套件性,增加或更換具體原型類都很方便。

原型模式的例項的拷貝包括淺複製和深複製:

  • 淺複製:將一個物件複製後,其基本資料型別的變數都會重新建立,而引用型別的變數指向的還是原物件所指向的,也就是指向的記憶體堆地址沒變。
  • 深複製:將一個物件複製後,不論是基本資料型別還是引用型別,都是重新建立的。

具體實現

Object類提供一個clone()方法,可以將一個Java物件複製一份。因此在Java中可以直接使用Object提供的clone()方法來實現物件的克隆,Java語言中的原型模式實現很簡單。
需要注意的是能夠實現克隆的Java類必須實現一個標識介面Cloneable,表示這個Java類支援被複制。如果一個類沒有實現這個介面但是呼叫了clone()方法,Java編譯器將丟擲一個CloneNotSupportedException異常。

我們以一封信為例:信中包括的資訊有寄信人,寄信時間和收信人,當成功寫完信的基本資訊的時候,其他人也想寫信,此時為了方便就直接以這封信為原型作為模板,然後進行裡面區域性的修改.

建立一個letter類,實現java中已經提供好的Cloneable介面
java

public class letter implements Cloneable{

	private String sender;
	private Date sendDate;
	private String addressee;
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object obj = super.clone();
		return obj;
	}

	public String getSender() {
		return sender;
	}

	public void setSender(String sender) {
		this.sender = sender;
	}

	public Date getSendDate() {
		return sendDate;
	}

	public void setSendDate(Date sendDate) {
		this.sendDate = sendDate;
	}

	public String getAddressee() {
		return addressee;
	}

	public void setAddressee(String addressee) {
		this.addressee = addressee;
	}

	public letter() {}
	
	public letter(String sender, Date sendDate, String addressee) {
		super();
		this.sender = sender;
		this.sendDate = sendDate;
		this.addressee = addressee;
	}
}
複製程式碼

客戶端

public class ClientLetter {
	
	public static void main(String[] args) throws CloneNotSupportedException {
		letter l = new letter("張三",new Date(System.currentTimeMillis()),"李四");
		System.out.println(l.getSender()+" "+l.getSendDate()+" "+l.getAddressee());
		//克隆
		letter l1 = (letter) l.clone();
		System.out.println(l1.getSender()+" "+l1.getSendDate()+" "+l1.getAddressee());
	}
}
複製程式碼

結果:
張三 Mon Apr 23 19:18:04 CST 2018 李四
張三 Mon Apr 23 19:18:04 CST 2018 李四


從結果可以看出克隆物件和原型物件保持一模一樣的內容,並且克隆物件(新物件)可以重新賦值.


上面的例子我們稱之為淺克隆,如果我們在程式碼中修改了時間的值,那麼克隆物件的值也會被修改.

public class ClientLetter {
	
	public static void main(String[] args) throws CloneNotSupportedException {
		Date date = new Date(System.currentTimeMillis());
		letter l = new letter("張三",date,"李四"); 
		//克隆
		letter l1 = (letter) l.clone();
		//修改時間屬性
		l.setSendDate(new Date(12345654321L));
		System.out.println(l.getSender()+" "+l.getSendDate()+" "+l.getAddressee());
		System.out.println(l1.getSender()+" "+l1.getSendDate()+" "+l1.getAddressee());
	}
}
複製程式碼

結果:
張三 Mon Apr 23 19:27:35 CST 2018 李四
張三 Sat Feb 14 07:27:34 CST 2009 李四


從結果可以看出當時間修改後克隆物件時間也會修改,因為他們兩個時間屬性指向的是同一個物件,我們克隆的時候只是把值包括引用地址都一起克隆過來,所以他們引用了同一個物件,此時稱之為淺複製.那麼有淺就有深,事物都有兩面,那麼什麼是深克隆呢?


深克隆:
在深克隆中,無論原型物件的成員變數是值型別還是引用型別,都將複製一份給克隆物件,深克隆將原型物件的所有引用物件也複製一份給克隆物件。簡單來說,在深克隆中,除了物件本身被複制外,物件所包含的所有成員變數也將複製.

public class letter implements Cloneable{

	private String sender;
	private Date sendDate;
	private String addressee;
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object obj = super.clone();
		//實現深克隆
		letter l = (letter) obj;
		l.sendDate = (Date) this.sendDate.clone(); //將屬性也克隆
		return obj;
	}

	public String getSender() {
		return sender;
	}

	public void setSender(String sender) {
		this.sender = sender;
	}

	public Date getSendDate() {
		return sendDate;
	}

	public void setSendDate(Date sendDate) {
		this.sendDate = sendDate;
	}

	public String getAddressee() {
		return addressee;
	}

	public void setAddressee(String addressee) {
		this.addressee = addressee;
	}
	
	public letter(String sender, Date sendDate, String addressee) {
		super();
		this.sender = sender;
		this.sendDate = sendDate;
		this.addressee = addressee;
	}
}
複製程式碼

客戶端

//深克隆
public class ClientLetter2 {
	
	public static void main(String[] args) throws CloneNotSupportedException {
		Date date = new Date(System.currentTimeMillis());
		letter l = new letter("張三",date,"李四"); 
		System.out.println(l.getSendDate());
		//克隆
		letter l1 = (letter) l.clone();
		l1.setSender("王五");
		//修改時間屬性
		l.setSendDate(new Date(12345654321L));
		System.out.println(l.getSender()+" "+l.getSendDate()+" "+l.getAddressee());
		System.out.println(l1.getSender()+" "+l1.getSendDate()+" "+l1.getAddressee());
	}
}
複製程式碼

從結果可以看出,深克隆技術實現了原型物件和克隆物件的完全獨立,對任意克隆物件的修改都不會給其他物件產生影響,是一種更為理想的克隆實現方式。

原型模式的優缺點

優點
上面我們說過,克隆模式(原型模式)類似於new ,但是不同於new.並且克隆模式建立物件的效率比使用普通new的物件需要的時間更短.在new物件需要很長時間時可以使用.
缺點
原型模式主要的缺陷就是每個原型必須含有 clone 方法,在已有類的基礎上來新增 clone 操作是比較困難的;而且當內部包括一些不支援copy或者迴圈引用的物件時,實現就更加困難了。

測試原型模式與普通new方法所需要的時間

模擬建立建立物件需要很長時間,對比new和clone建立物件需要的時間.

public class Letter implements Cloneable {
	public Letter() {
		try {
			Thread.sleep(10);	//模擬建立物件的時間需要很長
		}catch (Exception e) {
			// TODO: handle exception
		}
	}
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object obj = super.clone();
		return obj;
	}
}
複製程式碼
public class test {
	
	public static void main(String[] args) throws CloneNotSupportedException {
		test.testNew(1000);
		test.testClone(1000);
	}
	
	//普通方式建立
	public static void testNew(int size) {
		long start = System.currentTimeMillis();
		for(int i = 0;i<size;i++) {
			Letter le = new Letter();
		}
		long end = System.currentTimeMillis();
		System.out.println("new需要的時間"+(end-start));
	}
	
	//克隆方式建立
		public static void testClone(int size) throws CloneNotSupportedException {
			long start = System.currentTimeMillis();
			Letter le = new Letter();
			for(int i = 0;i<size;i++) {
				Letter le1 = (Letter) le.clone();
			}
			long end = System.currentTimeMillis();
			System.out.println("clone需要的時間"+(end-start));
		} 
}
複製程式碼

結果:
new需要的時間10099
clone需要的時間11

可以非常明顯的看出,在建立物件需要比較長的時間的時候克隆比new物件需要的時間短的多,但如果建立物件不需要太長時間的時候,new和clone的差距還是比較小的.

相關文章