04.原型模式設計思想
目錄介紹
- 01.原型模式介紹
- 1.1 原型模式由來
- 1.2 原型模式定義
- 1.3 原型模式場景
- 1.4 原型模式思考
- 02.原型模式原理與實現
- 2.1 羅列一個場景
- 2.2 用例子理解原型
- 2.3 案例演變分析
- 2.4 原型模式基本實現
- 03.原型模式分析
- 3.1 原型模式VS工廠模式
- 3.2 原型模式VS深複製
- 04.原型模式應用解析
- 4.1 使用clone方法
- 4.2 實現介面Cloneable
- 4.3 深克隆和淺克隆
- 05.原型模式總結
- 5.1 優缺點分析
- 5.2 有哪些弊端
- 5.3 應用環境說明
- 06.原型模式擴充應用
- 6.1 模式擴充套件
- 6.2 原型模式總結
- 6.3 更多內容推薦
01.原型模式介紹
1.0 AI生成部落格摘要
本文詳細介紹了原型模式的設計思想,包括其定義、應用場景、實現原理及優缺點。透過郵件複製的例子,闡述了原型模式如何透過克隆現有物件來建立新物件,從而提高效能和減少程式碼複雜度。文章還對比了原型模式與工廠模式的區別,並討論了深克隆和淺克隆的實現方式。最後,總結了原型模式在特定場景下的應用價值和侷限性。
1.1 原型模式由來
我們來看一個例子——郵件。由於郵件物件包含的內容較多(如傳送者、接收者、標題、內容、日期、附件等),某系統中現需要提供一個郵件複製功能,對於已經建立好的郵件物件,可以透過複製的方式建立一個新的郵件物件,如果需要改變某部分內容,無須修改原始的郵件物件,只需要修改複製後得到的郵件物件即可。
如果在每次複製的時候重新寫一遍這個程式碼,可想而知是多麼的複雜,所以這個時候需要封裝一個基類,把這些賦值的過程全部封裝好,由此可見,原型模式利用的無非就是物件導向程式設計的封裝、繼承特點。
1.2 原型模式定義
原型模式是透過給出一個原型物件來指明所建立的物件的型別,然後使用自身實現的克隆介面來複制這個原型物件,該模式就是用這種方式來建立出更多同型別的物件。
使用這種方式建立新的物件的話,就無需再透過 new 例項化來建立物件了。這是因為 Object 類的 clone 方法是一個本地方法,它可以直接操作記憶體中的二進位制流,所以效能相對 new 例項化來說,更佳。
原型模式的基本工作原理是透過將一個原型物件傳給那個要發動建立的物件,這個要發動建立的物件透過請求原型物件複製原型自己來實現建立過程。
1.3 原型模式場景
原型模式在以下情況下特別有用:
- 當物件的建立過程比較複雜或耗時時,可以透過複製現有物件來提高效能。
- 當需要建立多個相似物件,但又不希望與它們的具體類耦合時,可以使用原型模式。
原型模式的應用場景,以及它的兩種實現方式:深複製和淺複製。雖然原型模式的原理和程式碼實現非常簡單。
1.4 原型模式思考
提供了一種靈活的方式來建立物件,透過複製現有物件來建立新物件,從而避免了昂貴的物件建立過程。該模式的核心思想是透過複製現有物件來建立新物件,而不是透過例項化類來建立。
02.原型模式原理與實現
2.1 羅列一個場景
拿郵件例子程式碼來說,由於展示,我們封裝郵件中的標題、內容和附件三個欄位,其中附件是一個類,包含了名字、型別和檔案大小。
首先採用預設的clone方式(淺克隆),即複製後的郵件中的附件與原郵件中的附件是同一物件;然後實現深克隆,即複製後的郵件中的附件與原郵件中的附件不是同一物件。
2.2 用例子理解原型
1.基類——為了方便子類呼叫clone不需要捕獲異常
public class Prototype implements Cloneable {
public Prototype cloneMe() {
Prototype prototype = null;
try {
prototype = (Prototype) this.clone();
} catch (CloneNotSupportedException exception) {
}
return prototype;
}
}
2.附件子類
public class Attachment extends Prototype {
// 附件名字
private String name;
// 附件文件型別
private String type;
// 附件大小
private long length;
public Attachment(String name, String type, long length) {
super();
this.name = name;
this.type = type;
this.length = length;
}
public void download() {
System.out.println("下載了附件:" + name);
}
public String display() {
return "Attachment [name=" + name + ", type=" + type + ", length=" + length + "]";
}
@Override
public boolean equals(Object obj) {
Attachment a = (Attachment) obj;
if(a.name.equals(name) && a.type.equals(type) && a.length == length) {
return true;
}
return false;
}
}
3.郵件子類
public class Email extends Prototype {
// 標題
private String title;
// 內容
private String content;
// 附件
private Attachment attachment;
public Email(String title, String content, Attachment attachment) {
super();
this.title = title;
this.content = content;
this.attachment = attachment;
}
public String display() {
return "Email [title=" + title + ", content=" + content + ", attachment=" + attachment.display();
}
public Attachment getAttachment() {
return attachment;
}
@Override
public boolean equals(Object obj) {
Email e = (Email) obj;
if(e.title.equals(title) && e.content.equals(content) && e.attachment.equals(attachment)) {
return true;
}
return false;
}
}
4.客戶端類
private void test() {
Email email = new Email("郵件標題", "郵件內容,哈哈哈..", new Attachment("附件標題", "文件", 45987));
System.out.println(email.display());
Email copyEmail = (Email) email.cloneMe();
System.out.println("郵件複製狀態:" + (email != copyEmail && email.equals(copyEmail) ? "成功" : "失敗") );
Attachment attachment = email.getAttachment();
Attachment copyAttachment = copyEmail.getAttachment();
if(attachment.equals(copyAttachment)) {
System.out.println("郵件附件內容一致");
}
if(attachment == copyAttachment) {
System.out.println("郵件附件未複製");
} else {
System.out.println("郵件附件已複製");
}
}
最後執行結果如下所示:
Email [title=郵件標題, content=郵件內容,哈哈哈.., attachment=Attachment [name=附件標題, type=文件, length=45987]
郵件複製狀態:成功
郵件附件內容一致
郵件附件未複製
可以看到,由於重寫了子類的equals方法,所以當每個欄位的內容相等時,認為該類的內容相同,但是進行復制後,地址是不同的,最後一個判斷是用於判斷郵件類呼叫cloneMe方法後,當中的附件物件是否也進行了複製,若地址相同,所以未進行復制,此時是淺克隆。
2.3 案例演變分析
那麼如何進行深克隆呢?很簡單,只需要重寫郵件物件的cloneMe方法,先呼叫父類的克隆方法獲得自身複製的物件,然後自身的附件物件也呼叫cloneMe方法,將得到的物件再賦值給複製後的郵件物件。
public class EmailNew extends Prototype {
// 標題
private String title;
// 內容
private String content;
// 附件
private Attachment attachment;
public EmailNew(String title, String content, Attachment attachment) {
super();
this.title = title;
this.content = content;
this.attachment = attachment;
}
public String display() {
return "EmailNew [title=" + title + ", content=" + content + ", attachment=" + attachment.display();
}
public Attachment getAttachment() {
return attachment;
}
@Override
public boolean equals(Object obj) {
EmailNew e = (EmailNew) obj;
if(e.title.equals(title) && e.content.equals(content) && e.attachment.equals(attachment)) {
return true;
}
return false;
}
@Override
public EmailNew cloneMe() {
EmailNew e = (EmailNew) super.cloneMe();
e.attachment = (Attachment) attachment.cloneMe();
return e;
}
}
最後執行結果如下所示:
EmailNew [title=郵件標題, content=郵件內容,哈哈哈.., attachment=Attachment [name=附件標題, type=文件, length=45987]
郵件複製狀態:成功
郵件附件內容一致
郵件附件已複製
2.4 原型模式基本實現
原型模式包含如下角色:
- Prototype:抽象原型類。宣告一個克隆自己的介面。
- ConcretePrototype:具體原型類。實現原型類的 clone() 方法,實現克隆自己的操作。它是可被複制的物件,可以有多個。
- Client:客戶類。
要實現一個原型類,需要具備三個條件:
- 實現 Cloneable 介面:Cloneable 介面與序列化介面的作用類似,它只是告訴虛擬機器可以安全地在實現了這個介面的類上使用 clone 方法。在 JVM 中,只有實現了 Cloneable 介面的類才可以被複製,否則會丟擲 CloneNotSupportedException 異常。
- 重寫 Object 類中的 clone 方法:在 Java 中,所有類的父類都是 Object 類,而 Object 類中有一個 clone 方法,作用是返回物件的一個複製。
- 在重寫的 clone 方法中呼叫 super.clone():預設情況下,類不具備複製物件的能力,需要呼叫 super.clone() 來實現。
現在透過一個簡單的例子來實現一個原型模式:
//實現Cloneable 介面的原型抽象類Prototype
class Prototype implements Cloneable {
//重寫clone方法
public Prototype clone(){
Prototype prototype = null;
try{
prototype = (Prototype)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return prototype;
}
}
//實現原型類
class ConcretePrototype extends Prototype{
public void show(){
System.out.println("原型模式實現類");
}
}
public class Client {
public static void main(String[] args){
ConcretePrototype cp = new ConcretePrototype();
for(int i=0; i< 10; i++){
ConcretePrototype clonecp = (ConcretePrototype)cp.clone();
clonecp.show();
}
}
}
03.原型模式分析
3.1 原型模式VS工廠模式
目的和使用場景區別:
- 原型模式的主要目的是透過複製現有物件來建立新物件,而不是透過例項化類來建立。它適用於需要建立多個相似物件,但又不希望與具體類耦合的情況。
- 工廠模式的主要目的是封裝物件的建立過程,透過一個工廠類來統一建立物件。它適用於需要根據不同的條件或引數建立不同型別的物件的情況。
建立方式區別:
- 原型模式透過複製現有物件來建立新物件,可以是淺克隆(只複製基本型別屬性)或深克隆(複製所有屬性,包括引用型別屬性)。
- 工廠模式透過一個工廠類來建立物件,根據不同的條件或引數呼叫不同的工廠方法來建立不同型別的物件。
靈活性區別:
- 原型模式在執行時動態確定物件的型別,可以根據需要克隆不同型別的物件。
- 工廠模式在編譯時確定物件的型別,需要在工廠類中定義不同的工廠方法來建立不同型別的物件。
關注點不同:
- 原型模式關注物件的複製和克隆過程,透過複製現有物件來建立新物件。
- 工廠模式關注物件的建立過程,透過工廠類來封裝物件的建立邏輯。
3.2 原型模式VS深複製
原型模式(Prototype Pattern)和深複製(Deep Copy)是兩個概念,它們在物件複製和克隆方面有一些區別。
目的和使用場景:
- 原型模式的主要目的是透過複製現有物件來建立新物件,而不是透過例項化類來建立。它適用於需要建立多個相似物件,但又不希望與具體類耦合的情況。
- 深複製的主要目的是建立一個新物件,並將原始物件的所有屬性都複製到新物件中,包括引用型別的屬性。它適用於需要完全獨立的物件副本,而不是共享引用的情況。
複製方式:
- 原型模式透過複製現有物件來建立新物件,可以是淺克隆(只複製基本型別屬性)或深克隆(複製所有屬性,包括引用型別屬性)。
- 深複製是一種複製方式,它會遞迴地複製物件的所有屬性,包括引用型別屬性,確保新物件和原始物件完全獨立。
實現方式的差異:
- 原型模式可以透過實現Cloneable介面並重寫clone()方法來實現物件的複製和克隆。
- 深複製可以透過自定義的複製方法或使用序列化和反序列化來實現。
04.原型模式應用解析
4.1 使用clone方法
在原型模式結構中定義了一個抽象原型類,所有的Java類都繼承自java.lang.Object,而Object類提供一個clone()方法,可以將一個Java物件複製一份。
因此在Java中,可以直接使用Object提供的clone()方法來實現物件的克隆,Java語言中的原型模式實現很簡單。
4.2 實現介面Cloneable
能夠實現克隆的Java類必須實現一個標識介面Cloneable,表示這個Java類支援複製。
如果一個類沒有實現這個介面但是呼叫了clone()方法,Java編譯器將丟擲一個CloneNotSupportedException異常。
因此在Java中,Object類和Cloneable介面共同充當著抽象原型類,無需再次定義,不過由於呼叫clone方法需要捕獲異常,每次呼叫的時候做處理比較麻煩,所以可以抽象出一個基類,來捕獲,那麼在具體子類中就不需要再次捕獲(僅限於子類不需要處理異常的時候)。
4.3 深克隆和淺克隆
通常情況下,一個類包含一些成員物件,在使用原型模式克隆物件時,根據其成員物件是否也克隆,原型模式可以分為兩種形式:深克隆和淺克隆。
- 淺克隆:建立一個新物件,新物件的屬性和原來物件完全相同,對於非基本型別屬性,仍指向原有屬性所指向的物件的記憶體地址。
- 深克隆:建立一個新物件,屬性中引用的其他物件也會被克隆,不再指向原有物件地址。
Java中的clone方法預設是淺克隆。
關於資料深克隆和淺克隆,我這邊有一篇文章專門詳細介紹其案例和原理。具體可以看:各種複製資料比較
05.原型模式總結
5.1 優缺點分析
優點
- 當建立新的物件例項較為複雜時,使用原型模式可以簡化物件的建立過程,透過一個已有例項可以提高新例項的建立效率。
- 可以動態增加或減少產品類。
- 原型模式提供了簡化的建立結構。
- 可以使用深克隆的方式儲存物件的狀態。
5.2 有哪些弊端
缺點
- 需要為每一個類配備一個克隆方法,而且這個克隆方法需要對類的功能進行通盤考慮,這對全新的類來說不是很難,但對已有的類進行改造時,不一定是件容易的事,必須修改其原始碼,違背了“開閉原則”。
- 在實現深克隆時需要編寫較為複雜的程式碼。要修改clone方法的實現,有一定複雜。
5.3 應用環境說明
在以下情況下可以使用原型模式:
- 建立新物件成本較大,新的物件可以透過原型模式對已有物件進行復制來獲得,如果是相似物件,則可以對其屬性稍作修改。
- 如果系統要儲存物件的狀態,而物件的狀態變化很小,或者物件本身佔記憶體不大的時候,也可以使用原型模式配合備忘錄模式來應用。相反,如果物件的狀態變化很大,或者物件佔用的記憶體很大,那麼採用狀態模式會比原型模式更好。
- 需要避免使用分層次的工廠類來建立分層次的物件,並且類的例項物件只有一個或很少的幾個組合狀態,透過複製原型物件得到新例項可能比使用建構函式建立一個新例項更加方便。
06.原型模式擴充應用
6.1 模式擴充套件
6.2 原型模式總結
1.什麼是原型模式?
如果物件的建立成本比較大,而同一個類的不同物件之間差別不大(大部分欄位都相同),在這種情況下,我們可以利用對已有物件(原型)進行復制(或者叫複製)的方式,來建立新物件,以達到節省建立時間的目的。這種基於原型來建立物件的方式就叫作原型設計模式,簡稱原型模式。
2.原型模式的兩種實現方法
原型模式有兩種實現方法,深複製和淺複製。淺複製只會複製物件中基本資料型別資料和引用物件的記憶體地址,不會遞迴地複製引用物件,以及引用物件的引用物件……而深複製得到的是一份完完全全獨立的物件。所以,深複製比起淺複製來說,更加耗時,更加耗記憶體空間。
如果要複製的物件是不可變物件,淺複製共享不可變物件是沒問題的,但對於可變物件來說,淺複製得到的物件和原始物件會共享部分資料,就有可能出現資料被修改的風險,也就變得複雜多了。
6.3 更多內容推薦
模組 | 描述 | 備註 |
---|---|---|
GitHub | 多個YC系列開源專案,包含Android元件庫,以及多個案例 | GitHub |
部落格彙總 | 匯聚Java,Android,C/C++,網路協議,演算法,程式設計總結等 | YCBlogs |
設計模式 | 六大設計原則,23種設計模式,設計模式案例,物件導向思想 | 設計模式 |
Java進階 | 資料設計和原理,物件導向核心思想,IO,異常,執行緒和併發,JVM | Java高階 |
網路協議 | 網路實際案例,網路原理和分層,Https,網路請求,故障排查 | 網路協議 |
計算機原理 | 計算機組成結構,框架,儲存器,CPU設計,記憶體設計,指令程式設計原理,異常處理機制,IO操作和原理 | 計算機基礎 |
學習C程式設計 | C語言入門級別系統全面的學習教程,學習三到四個綜合案例 | C程式設計 |
C++程式設計 | C++語言入門級別系統全面的教學教程,併發程式設計,核心原理 | C++程式設計 |
演算法實踐 | 專欄,陣列,連結串列,棧,佇列,樹,雜湊,遞迴,查詢,排序等 | Leetcode |
Android | 基礎入門,開源庫解讀,效能最佳化,Framework,方案設計 | Android |
23種設計模式
23種設計模式 & 描述 & 核心作用 | 包括 |
---|---|
建立型模式 提供建立物件用例。能夠將軟體模組中物件的建立和物件的使用分離 |
工廠模式(Factory Pattern) 抽象工廠模式(Abstract Factory Pattern) 單例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern) |
結構型模式 關注類和物件的組合。描述如何將類或者物件結合在一起形成更大的結構 |
介面卡模式(Adapter Pattern) 橋接模式(Bridge Pattern) 過濾器模式(Filter、Criteria Pattern) 組合模式(Composite Pattern) 裝飾器模式(Decorator Pattern) 外觀模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) |
行為型模式 特別關注物件之間的通訊。主要解決的就是“類或物件之間的互動”問題 |
責任鏈模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 直譯器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 備忘錄模式(Memento Pattern) 觀察者模式(Observer Pattern) 狀態模式(State Pattern) 空物件模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 訪問者模式(Visitor Pattern) |