原型模式(Prototype Pattern)。

孤芳不自賞發表於2017-08-16

定義:

用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。

不通過new關鍵字來產生一個物件,而是通過物件複製來實現的模式就叫做原型模式。

原型模式的核心就是一個clone方法,通過該方法進行物件的拷貝,Java提供了一個Cloneable介面來標示這個物件是可拷貝的,為什麼說是“標示”呢?翻開JDK的幫助看看Cloneable是一個方法都沒有的,這個介面只是一個標記作用,在JVM中具有這個標記的物件才有可能被拷貝。

通用原始碼:

public class PrototypeClass implements Cloneable {

// 覆寫父類Object方法

@Override

public PrototypeClass clone() {

PrototypeClass prototypeClass = null;

try{

prototypeClass = (PrototypeClass) super.clone();

} catch(CloneNotSupportedException e) {

// 異常處理

}

return prototypeClass;

}

}

優點:

  • 效能優良

原型模式是在記憶體二進位制流的拷貝,要比直接new一個物件效能好很多,特別是要在一個迴圈體內產生大量的物件時,原型模式可以更好地體現其優點。

  • 逃避建構函式的約束

這既是它的優點也是缺點,直接在記憶體中拷貝,建構函式是不會執行的。優點就是減少了約束,缺點就是減少了約束,需要大家在實際應用時考慮。

使用場景:

  • 資源優化場景

類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等。

  • 效能和安全要求的場景

通過new產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式。

  • 一個物件多個修改者的場景

一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用。

在實際專案中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過clone的方法建立一個物件,然後由工廠方法提供給呼叫者。

注意事項:

  • 建構函式不會被執行

Object類的clone方法的原理是從記憶體中(具體地說是堆記憶體)以二進位制流的方式進行拷貝,重新分配一個記憶體塊,那建構函式沒有被執行也是非常正常的了。

  • 淺拷貝

Object類提供的方法clone只是拷貝本物件,其物件內部的陣列、引用物件等都不拷貝,還是指向原生物件的內部元素地址,這種拷貝就叫做淺拷貝。確實是非常淺,兩個物件共享了一個私有變數,你改我改大家都能改,這是一種非常不安全的方式,在實際專案中使用還是還是比較少的(當然,這也是一種“危機”環境的一種救命方式)。內部的陣列和引用物件才不拷貝,其他的原始型別如int、long、char燈都會被拷貝,但是對於String型別,Java就希望你把它認為是基本型別自,它是沒有clone方法的,處理機制也比較特殊,通過字串池(stringpool)在需要的時候才在記憶體中建立新的字串,讀者在使用的時候就把String當做基本類使用即可。

注意:使用原型模式時,引用的成員變數必須滿足兩個條件才不會被拷貝:一是類的成員變數,而不是方法內變數;二十必須是一個可變的引用物件,而不是一個原始型別或不可變物件。

  • 深拷貝

你修改你的,我修改我的,不相互影響,這種拷貝就叫做深拷貝。深拷貝還有一種實現方法就是通過自己寫二進位制流來操作物件,然後實現物件的深拷貝。

注意:深拷貝和淺拷貝建議不要混合使用,特別是在涉及類的繼承時,父類有多個引用的情況就非常複雜,建議的方案是深拷貝和淺拷貝分開實現。

  • clone與final兩個冤家

注意:要使用clone方法,類的成員變數上不要增加final關鍵字。

最佳實踐:

原型模式先產生出一個包含大量共有資訊的類,然後可以拷貝出副本,修正細節資訊,建立了一個完整地個性物件。

示例程式碼:

廣告信模板程式碼

public class AdvTemplate {

// 廣告信名稱

private String advSubject = "XX銀行國慶信用卡抽獎活動";

// 廣告信內容

private String advContext = "國慶抽獎活動通知:只要刷卡就送你一百萬!...";

// 取得廣告信的名稱

public String getAdvSubject() {

return this.advSubject();

}

// 取得廣告信的內容

public String getAdvContext() {

return this.advContext;

}

}

// 郵件類

public class Mail implements Cloneable {

// 收件人

private String receiver;

// 郵件名稱

private String subject;

// 稱謂

private String appellation;

// 郵件內容

private String context;

//  郵件的尾部,一般都是加上“XXX版權所有”等資訊

private String tail;

// 建構函式

public Mail(AdvTemplate advTemplate) {

this.context = advTemplate.getAdvContext();

this.subject = advTemplate.getAdvSubject();

}

@Override

public Mail clone() {

Mail mail = null;

try {

mail = (Mail) super.clone();

} catch(CloneNotSupportedException e) {

e.printStackTrace();

}

return mail;

}

// 以下為getter/setter方法,省略

}

場景類

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 cloneMail = mail.clone();

cloneMail.setAppelation(getRandString(5) + "先生(女士)");

cloneMail.setReceiver(getRandString(8) + ".com");

// 然後傳送郵件

sendMail(cloneMail);

i++;

}

}

// 傳送郵件

public static void sendMail(Mail mail) {

System.out.println("標題:" + mail.getSubject() + "\t收件人: " + mail.getReceiver() + "\t...傳送成功!");

}

// 獲得指定長度的隨機字串

public static String getRandString(int maxLength) {

String source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

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 class Thing implements Cloneable {

public Thing() {

System.out.println("建構函式被執行了...");

}

@Override

public Thing clone() {

Thing thing = null;


try{

thing = (Thing ) super.clone();

} catch(CloneNotSupportedException e) {

// 異常處理

}

return thing;

}

}

簡單的場景類

public class Client {

public static void main(String[] args) {

// 產生一個物件

Thing thing = new Thing();

// 拷貝一個物件

Thing cloneThing = thing.clone();

}

}

淺拷貝

public class Thing implements Cloneable {

// 定義一個私有變數

private ArrayList<String> arrayList = new ArrayList<String>();

@Override

public Thing clone() {

Thing thing = null;

try{

thing = (Thing ) super.clone();

} catch(CloneNotSupportedException e) {

// 異常處理

}

return thing;

}

// 設定arrayList的值

public void setValue(String value) {

this.arrayList.add(value);

}

// 取得arrayList的值

public ArrayList<String getValue() {

return this.arrayLIst;

}

}

淺拷貝測試

public class Client {

public static void main(String[] args) {

// 產生一個物件

Thing thing = new Thing();

// 設定一個值

thing.setValue("張三");

//拷貝一個物件

Thing cloneThing = thing.clone();

cloneThing.setValue("李四");

System.out.println(thing.getValue());

}

}

深拷貝

public class Thing implements Cloneable {

// 定義一個私有變數

private ArrayList<String> arrayList = new ArrayList<String>();

@Override

public Thing clone() {

Thing thing = null;

try{

thing = (Thing ) super.clone();

thing.arrayList = (ArrayLIst<String>) this. arrayList.clone();

} catch(CloneNotSupportedException e) {

// 異常處理

}

return thing;

}

// 設定arrayList的值

public void setValue(String value) {

this.arrayList.add(value);

}

// 取得arrayList的值

public ArrayList<String getValue() {

return this.arrayLIst;

}

}

 

相關文章