天天看點

【設計模式二十之原型模式】原型模式詳解細說原型模式

原型模式Prototype Pattern

  • 細說原型模式
    • 細說原型模式
      • 定義
      • UML模型
        • 基于UML的代碼
      • 場景
        • 場景一
      • 代碼
        • 代碼一
      • 基于原型模式的深拷貝與淺拷貝
        • 淺拷貝
        • 深拷貝
        • 原型模式clone與final冤家路窄
      • 原型模式應用和注意事項

細說原型模式

提示:

部落客:章飛 _906285288的部落格

部落格位址:http://blog.csdn.net/qq_29924041

細說原型模式

原型模式這個模式的簡單程度是僅次于單例模式和疊代器模式,非常簡單,但是要使

用好這個模式還有很多注意事項。原型,顧名思義,也就是本來的模型隻有一個,類似電子工業生産中的模具,有了這個模具之後,後面所有的産品都可以由這個模具生産出來,但是這個模具生産出來後,可以完全一樣,也可以在生産的産品後再修改出一部分差異化的東西。這就是原型模式。

定義

原型模式:用原型執行個體指定建立對象的種類,通過拷貝這些原型來建立新的對象。

其實也就是使用java的拷貝原理,在原型的基礎之上建立新的對象而已。

UML模型

【設計模式二十之原型模式】原型模式詳解細說原型模式

從上述的UML圖中就可以看到,原型模式是非常簡單的,原型類中隻有clone一個方法,而在java中提供了Cloneable這個接口來辨別對象的可拷貝特性。這個接口其實隻是一個标記作用,在JVM中通過這個标記來識别出這個對象具有可拷貝的特性。

基于UML的代碼

package src.com.zzf.designpattern.prototypepattern.demo4;

/**
 * 原型對象,實作Cloneable接口,重寫clone方法
 * @author zhouzhangfei
 *
 */
public class PrototypeClass implements Cloneable{
	public String name = "123";
	
	//重寫clone方法
	@Override
	protected PrototypeClass clone()  {
		// TODO Auto-generated method stub
		PrototypeClass prototypeClass = null;
		try {
			prototypeClass =  (PrototypeClass) super.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return prototypeClass;
	}
}

           
package src.com.zzf.designpattern.prototypepattern.demo4;

/**
 * 測試代碼,通過拷貝方法,建立出了兩個不同的對象
 * @author zhouzhangfei
 *
 */
public class Test {
	public static void main(String[] args) {
		//原型模式建立原始對象
		PrototypeClass prototypeClass = new PrototypeClass();
		System.out.println(prototypeClass.toString()+"\t"+prototypeClass.name);
		//通過克隆拷貝方法,建立出一個新的對象
		PrototypeClass prototypeClass2 = prototypeClass.clone();
		System.out.println(prototypeClass2.toString()+"\t"+prototypeClass2.name);
		
		//從上面可以看出,基本資料類型的拷貝是随着對象一起被克隆出去的,即兩個對象的基本資料類型值是一緻的
	}
}


           

場景

在之前所有關于設計模式的部落格中都講到了場景,同樣,原型模式在實際的應用過程中也有非常多的場景。

場景一

設計模式之禅中舉了一個群發郵件的案例,定義一個郵件的模闆類,通過改變郵件的收件者和内容,然後修改郵件的發送。這個在實際開發過程中是有很廣泛的應用的。如果通過new的方式,會造成大量的性能浪費,而使用原型模式,通過記憶體已有的對象,把對象拷貝一份,産生一個新的對象,和原有對象一樣,然後再修改細節的資料。有助于提高性能

代碼

代碼一

package src.com.zzf.designpattern.prototypepattern.demo1;


/**
 * 廣告信的模闆
 * @author Administrator
 *
 */
public class AdvTemplate {
	private String advSubject = "XX銀行信用卡抽獎活動";
	private String advContext = "國慶抽獎活動:隻要刷卡就送你一百萬!...";
	public String getAdvSubject() {
		return advSubject;
	}
	public void setAdvSubject(String advSubject) {
		this.advSubject = advSubject;
	}
	public String getAdvContext() {
		return advContext;
	}
	public void setAdvContext(String advContext) {
		this.advContext = advContext;
	}
	
}
           
package src.com.zzf.designpattern.prototypepattern.demo1;


public class Mail implements Cloneable{
	//收件人
	String receiver;
	//郵件名稱
	String subject;
	//稱謂
	String appellation;
	//郵件内容
	String context;
	//郵件的尾部
	String tail;
	
	public Mail(AdvTemplate mAdvTemplate) {
		this.context = mAdvTemplate.getAdvContext();
		this.subject = mAdvTemplate.getAdvSubject();
	}
	public String getReceiver() {
		return receiver;
	}
	public void setReceiver(String receiver) {
		this.receiver = receiver;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getAppellation() {
		return appellation;
	}
	public void setAppellation(String appellation) {
		this.appellation = appellation;
	}
	public String getContext() {
		return context;
	}
	public void setContext(String context) {
		this.context = context;
	}
	public String getTail() {
		return tail;
	}
	public void setTail(String tail) {
		this.tail = tail;
	}
	
	@Override
	protected Mail clone() {
		// TODO Auto-generated method stub
		Mail mail = null;
		try {
			mail = (Mail) super.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return mail;
	}
}

           
package src.com.zzf.designpattern.prototypepattern.demo1;


import java.util.Random;
/**
 * 一是類初始化需要消化非常多的資源,這個資源包括資料、硬體資源
等;二是通過new 産生一個對象需要非常繁瑣的資料準備或通路權限,則可以使用原型模式;
三是一個對象需要提供給其他對象通路,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對
象供調用者使用。在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過clone
的方法建立一個對象,然後由工廠方法提供給調用者。
 * @author Administrator
 *
 */
public class Client {
	// 發送賬單的數量,這個值是從資料庫中獲得
	private static int MAX_COUNT = 6;

	public static void main(String[] args) {
		// 模拟發送郵件
		int i = 0;
		// 把模闆定義出來,這個是從資料庫中獲得
		Mail mail = new Mail(new AdvTemplate());
		mail.setTail("XX銀行版權所有");

		while (i < MAX_COUNT) {
			// 以下是每封郵件不同的地方
			Mail mailclone = mail.clone();
			mailclone.setAppellation(getRandString(5) + " 先生(女士)");
			mailclone.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");
			// 然後發送郵件
			sendMail(mailclone);
			i++;
		}
	}

	// 獲得指定長度的随機字元串
	public static String getRandString(int maxLength) {
		String source = "abcdefghijklmnopqrskuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
		StringBuffer sb = new StringBuffer();
		Random rand = new Random();
		for (int i = 0; i < maxLength; i++) {
			sb.append(source.charAt(rand.nextInt(source.length())));
		}
		return sb.toString();
	}

	// 發送郵件
	public static void sendMail(Mail mail) {
		System.out.println("标題:" + mail.getSubject() + "\t收件人:" + mail.getReceiver() + "\t....發送成功!");
	}
	
}
           

注意:

對象拷貝時,類的構造函數是不會被執行的

基于原型模式的深拷貝與淺拷貝

既然原型模式是通過拷貝的形式來進行的,那也就有必要分析一下什麼叫做淺拷貝,什麼叫深拷貝

淺拷貝

淺拷貝(Shallow Copy):①對于資料類型是基本資料類型的成員變量,淺拷貝會直接進行值傳遞,也就是将該屬性值複制一份給新的對象。因為是兩份不同的資料,是以對其中一個對象的該成員變量值進行修改,不會影響另一個對象拷貝得到的資料。②**對于資料類型是引用資料類型的成員變量,**比如說成員變量是某個數組、某個類的對象等,那麼淺拷貝會進行引用傳遞,也就是隻是将該成員變量的引用值(記憶體位址)複制一份給新的對象。因為實際上兩個對象的該成員變量都指向同一個執行個體。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的該成員變量值。

如下描述代碼所示:

package src.com.zzf.designpattern.prototypepattern.demo2;


/**
 * 淺拷貝對象,
 * @author zhouzhangfei
 *
 */
public class Thing implements Cloneable{
	//基本資料類型,int,String,long會直接拷貝一份過去
	public String nameString ="12345";
	public String string = new String("9999999");
	//數組對象不會直接拷貝,而是會将其位址拷貝一份過去,導緻兩個對象會通路同一塊記憶體區域
	public int [] array = new int[]{};
	public  Thing() {
		System.out.println("構造函數被執行了");
	}
	
	@Override
	protected Thing clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		Thing mThing = null;
		mThing = (Thing) super.clone();
		return mThing;
	}
	
	
}

           
package src.com.zzf.designpattern.prototypepattern.demo2;


/**
 * 淺拷貝
 * @author Administrator
 * 内部的數組和引用對象不拷貝,其他的原始類型比如int,long,String(Java 就希望你把String 認為是基本類型,String 是沒有clone 方法的)等都會被拷貝的。
 */
public class Test {
	public static void main(String[] args) {
		Thing mThing = new Thing();
		System.err.println(mThing.nameString);
		System.out.println(mThing.array);
		try {
			Thing cloneThing = mThing.clone();
			cloneThing.nameString = "445567";
			System.out.println(cloneThing.nameString);
			System.out.println(cloneThing.string);
			System.out.println(cloneThing.array);
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

           

運作結果為:

構造函數被執行了

12345

[[email protected]

445567

9999999

[[email protected]

從結果中就可以看到。對于String,new String等資料類型結構,是直接在新的對象中拷貝了一份,而對于數組對象來說,則是把原型對象中的數組的位址拷貝了一份給新的對象,這就會導緻兩個數組對象會同時去通路同一塊記憶體位址。這就是導緻一種叫做髒資料的現象

深拷貝

深拷貝(Deep Copy):對原型對象的所有資料結構都進行了拷貝。也就是對原型對象中的引用資料類型,集合,數組,等都在新的拷貝對象中開辟了一個新的空間。

如下案例所示:

package src.com.zzf.designpattern.prototypepattern.demo3;


import java.util.ArrayList;
/**
 * Clone 與final 兩對冤家
 * 删除掉final 關鍵字,這是最便捷最安全最快速的方式,你要使用clone 方法就在類
的成員變量上不要增加final 關鍵字
 * @author Administrator
 *
 */
public class Thing implements Cloneable{
	
	private ArrayList<String> arrayList = new ArrayList<String>();
	public int [] array = new int[]{};
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		Thing thing = null;
		thing = (Thing) super.clone();
		thing.arrayList = (ArrayList<String>) this.arrayList.clone();
		this.array = this.array.clone();
		return thing;
	}
	
	public void setValue(String obj) {
		this.arrayList.add(obj);
	}
	
	public ArrayList<String> getValue() {
		return arrayList;
	}
}

           
package src.com.zzf.designpattern.prototypepattern.demo3;


/**
 * 
 * @author Administrator
 *
 */
public class Client {
	
	public static void main(String[] args) {
		//産生一個對象
		Thing thing = new Thing();
		//設定一個值
		thing.setValue("張三");
		
		//拷貝一個對象
		Thing cloneThing = null;
		try {
			cloneThing = (Thing) thing.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		cloneThing.setValue("李四");
		System.out.println(thing.array);
		System.out.println(thing.getValue());
		System.out.println(cloneThing.getValue());
		System.out.println(cloneThing.array);
	}
	
}

           

執行結果為:

[[email protected]

[張三]

[張三, 李四]

[[email protected]

從上面結果中可以看到。數組對象位址發生了改變,也就是數組對象是重新開辟了一塊記憶體區域,而集合對象也發生了改變,拷貝對象中包含了原型對象中的元素,但是拷貝對象在修改完自己内部的集合的時候,并不會引起原型對象集合内容的改變,這就是深度拷貝,拷貝的不僅僅是基本資料類型,會将引用資料類型的位址在新的對象中重新開辟一份出來

原型模式clone與final冤家路窄

final關鍵字我們都知道是不能改變的對吧,而clone是為了拷貝而存在的,clone對象的屬性是随時都可以發生改變的,這也就造成了魚與熊掌不可兼得的問題。

解決方式:

删除掉final 關鍵字,這是最便捷最安全最快速的方式,你要使用clone 方法就在類

的成員變量上不要增加final 關鍵字

原型模式應用和注意事項

首先說優點吧:

1:原型模式是在記憶體的二進制流中進行的拷貝動作,。是以它會比new對象來的更快一些,也就是性能上會優化很多,尤其是在一個循環體内部産生大量對象的時候,這個時候非常推薦使用原型模式

2:原型模式在拷貝的時候構造函數是不會去執行的。

場景:

當一個類在初始化的時候比較消耗資源的時候,這個時候原型模式是一個好的選擇

當在循環體中大量建立一個對象的時候,這個時候原型模式也是一個極佳的選擇

注意:

淺拷貝和深拷貝的差別…不要在拷貝的時候,犯錯

歡迎繼續通路,我的部落格

繼續閱讀