原型模式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:原型模式在拷貝的時候構造函數是不會去執行的。
場景:
當一個類在初始化的時候比較消耗資源的時候,這個時候原型模式是一個好的選擇
當在循環體中大量建立一個對象的時候,這個時候原型模式也是一個極佳的選擇
注意:
淺拷貝和深拷貝的差別…不要在拷貝的時候,犯錯
歡迎繼續通路,我的部落格