設計模式—— 十三 :原型模式
文章目錄

什么是原型模式?
個性化電子賬單
使用原型模式前
使用原型模式后
原型模式的優缺點
原型模式的優點
原型模式的缺點
深克隆與淺克隆
淺克隆
深克隆
原型模式的應用場景
什么是原型模式?
原型模式是一個比較簡單,但應用頻率比較高的設計模式。
原型模式的通用類圖如下:
原型模式的核心是一個clone方法,通過該方法進行對象的拷貝,Java 提供了一個Cloneable接口來標示這個對象是可拷貝的,Cloneable接口的作用是標記,在JVM中具有這個標記的對象才有可能被拷貝。那怎么才能從“有可能被拷貝”轉換為“可以被拷貝”呢?方法是覆蓋 clone()方法:
@Override public Mail clone(){}
1
在clone()方法上增加了一個注解@Override——因為覆寫了Object類的clone方法。
在Java中原型模式非常簡單,通用源碼如下:
public class PrototypeClass implements Cloneable{ //覆寫父類Object方法 @Override public PrototypeClass clone(){ PrototypeClass prototypeClass = null; try { prototypeClass = (PrototypeClass)super.clone(); } catch (CloneNotSupportedException e) { //異常處理 } return prototypeClass; } }
1
2
3
4
5
6
7
8
9
10
11
12
個性化電子賬單
現在有這樣的業務場景:
每到月初的時候銀行會給信用卡用戶以郵件的方式發送一份電子賬單,包含用戶的消費情況、積分等,當然了,還有比較討厭的廣告信。出于個性化服務和投遞成功率的考慮,廣告信也作為電子賬單系統的一個子功能。
這個功能大概這么實現:指定一個模板,從數據庫中把客戶的信息一個一個地取出,放到模板中生成一份完整的郵件, 然后由發送機進行發送處理。
使用原型模式前
結合上面的實現思路,相應的類圖如下:
AdvTemplate是廣告信的模板,一般都是從數據庫取出,生成一個BO或者是 DTO,這里使用一個靜態的值來作代表;Mail是郵件類,發送機發送的就是這個類。
AdvTemplate類:
/** * @author 三分惡 * @date 2020年5月14日 * @description 廣告模板類 */ public class AdvTemplate { //廣告信名稱 private String advSubject ="XX銀行國慶信用卡抽獎活動"; //廣告信內容 private String advContext = "國慶抽獎活動通知:只要刷卡就送你一百萬!..."; public String getAdvSubject() { return advSubject; } public String getAdvContext() { return advContext; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Mail類:
/** * @author 三分惡 * @date 2020年5月14日 * @description 郵件類 */ public class Mail { //收件人 private String receiver; //郵件名稱 private String subject; //稱謂 private String appellation; //郵件內容 private String contxt; //郵件的尾部,一般都是加上"XXX版權所有"等信息 private String tail; //構造方法 public Mail(AdvTemplate advTemplate) { this.contxt = advTemplate.getAdvContext(); this.subject = advTemplate.getAdvSubject(); } //省略getter、setter方法 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Client場景類:
/** * @author 三分惡 * @date 2020年5月14日 * @description 場景類 */ 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.setAppellation(getRandString(5) + " 先生(女士)"); mail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com"); // 然后發送郵件 sendMail(mail); i++; } } // 發送郵件 public static void sendMail(Mail mail) { System.out.println("標題:" + mail.getSubject() + "\t收件人: " + mail.getReceiver() + "\t...發送成功!"); } // 獲得指定長度的隨機字符串 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(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
運行結果:
OK,發送廣告信的功能到此實現了。
但是存在一個問題,這個程序時單線程的,按照一封郵件發出去需要0.02秒,600萬封郵件需要33個小時,也就是一個整天都發送不完,今天的沒發送完,明天的賬單又產生了。
如果用多線程的方式呢?那么線程安全的問題又來了。產生第一封郵件對象,放到線程1中運行,還沒有發送出去;線程2也啟動了,直接就把郵件對 象mail的收件人地址和稱謂修改了。
解決的辦法有很多,其中一種就是通過原型模式。
使用原型模式后
類圖稍作修改:
Mail類實現Cloneable接口,覆寫clone()方法:
/** * @author 三分惡 * @date 2020年5月14日 * @description 郵件類 */ public class Mail implements Cloneable{ //收件人 private String receiver; //郵件名稱 private String subject; //稱謂 private String appellation; //郵件內容 private String contxt; //郵件的尾部,一般都是加上"XXX版權所有"等信息 private String tail; //構造方法 public Mail(AdvTemplate advTemplate) { this.contxt = advTemplate.getAdvContext(); this.subject = advTemplate.getAdvSubject(); } /** * 覆寫clone方法 */ @Override public Mail clone(){ Mail mail=null; try { mail=(Mail) super.clone(); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return mail; } //省略getter、setter方法 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Client場景類的修改:
public static void main(String[] args) { // 模擬發送郵件 int i = 0; // 把模板定義出來 Mail mail = new Mail(new AdvTemplate()); mail.setTail("XX銀行版權所有"); while (i < MAX_COUNT) { // 以下是每封郵件不同的地方 //這里使用clone方法clone對象 Mail cloneMail = mail.clone(); //使用clone的對象 cloneMail.setAppellation(getRandString(5) + " 先生(女士)"); cloneMail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com"); // 然后發送郵件 sendMail(mail); i++; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在設置郵件不同屬性的地方通過clone的方式產生一個新的對象,然后再修改細節的數據,如設置稱謂、設置收件人地址,這樣即使是多線程也不受影響。
原型模式的優缺點
原型模式的優點
原型模式的主要優點如下:
當創建新的對象實例較為復雜時,使用原型模式可以簡化對象的創建過程,通過復制一個已有實例可以提高新實例的創建效率。
原型模式提供了簡化的創建結構,工廠方法模式常常需要有一個與產品類等級結構相同的 工廠等級結構,而原型模式就不需要這樣,原型模式中產品的復制是通過封裝在原型類中的克隆方法實現的,無須專門的工廠類來創建產品。
可以使用深克隆的方式保存對象的狀態,使用原型模式將對象復制一份并將其狀態保存起 來,以便在需要的時候使用(如恢復到某一歷史狀態),可輔助實現撤銷操作。
原型模式的缺點
原型模式的主要缺點如下:
需要為每一個類配備一個克隆方法,而且該克隆方法位于一個類的內部,當對已有的類進 行改造時,需要修改源代碼,違背了“開閉原則”。
在實現深克隆時需要編寫較為復雜的代碼,而且當對象之間存在多重的嵌套引用時,為了 實現深克隆,每一層對象對應的類都必須支持深克隆,實現起來可能會比較麻煩。
深克隆與淺克隆
在Java語言中,數據類型分為值類型(基本數據類型)和引用類型,值類型包括 int、double、byte、boolean、char等簡單數據類型,引用類型包括類、接口、數組等復雜類 型。淺克隆和深克隆的主要區別在于是否支持引用類型的成員變量的復制。
淺克隆
在淺克隆中,如果原型對象的成員變量是值類型,將復制一份給克隆對象;如果原型對象的 成員變量是引用類型,則將引用對象的地址復制一份給克隆對象,也就是說原型對象和克隆 對象的成員變量指向相同的內存地址。簡單來說,在淺克隆中,當對象被復制時只復制它本 身和其中包含的值類型的成員變量,而引用類型的成員對象并沒有復制。
來看一個實例:
在Thing類中定義一個私有變量arrayLis,類型為ArrayList,然后通過setValue和getValue 分別進行設置和取值
/** * @author 三分惡 * @date 2020年5月14日 * @description */ public class Thing implements Cloneable{ //定義一個私有變量 private ArrayList
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在場景類中克隆
/** * @author 三分惡 * @date 2020年5月14日 * @description 淺克隆測試 */ public class ShallowCloneClient { public static void main(String[] args) { // 產生一個對象 Thing thing = new Thing(); // 設置一個值 thing.setValue("二錘子"); // 拷貝一個對象 Thing cloneThing = thing.clone(); cloneThing.setValue("三棒子"); System.out.println(thing.getValue()); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在這個例子中,對象thing和cloneThing的arrayList都指向了同一個地址,所以運行結果:
這里就存在風險,兩個對象共享了一個私有變量,都能對這個變量進行修改。
使用原型模式時,引用的成員變量必須滿足兩個條件才不會被克隆:一是類的成 員變量,而不是方法內變量;二是必須是一個可變的引用對象,而不是一個原始類型或不可 變對象。
深克隆
在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將復制一份給克隆對象, 深克隆將原型對象的所有引用對象也復制一份給克隆對象。簡單來說,在深克隆中,除了對象本身被復制外,對象所包含的所有成員變量也將復制。
將Thing類的clone方法進行修改:
@Override public Thing clone() { Thing thing = null; try { thing = (Thing) super.clone(); //對私有的類變量進行獨立的拷貝 thing.arrayList = (ArrayList
1
2
3
4
5
6
7
8
9
10
11
12
13
改動很少,對私有的類變量進行獨立的拷貝,這樣一來,兩個對象的arrayList的指向地址就不一樣了。
還可以通過序列化的方式實現,序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在于內存 中。通過序列化實現的拷貝不僅可以復制對象本身,而且可以復制其引用的成員對象,因此通過序列化將對象寫到一個流中,再從流里將其讀出來,可以實現深克隆。
原型模式的應用場景
資源優化場景
類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等。
性能和安全要求的場景
通過new產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式。
一個對象多個修改者的場景 一個對象需要提供給其他對象訪問,而且各個調用者可能都需要修改其值時,可以考慮 使用原型模式拷貝多個對象供調用者使用。
在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過clone的 方法創建一個對象,然后由工廠方法提供給調用者。
?? 設計模式—— 十二 :代理模式
?? 設計模式—— 十四 :中介者模式
參考:
【1】:《設計模式之禪》
【2】:《design-pattern-java》
【3】:《大話設計模式》
【4】:《設計模式之禪》
Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。