目錄
- 一、 啥是原型模式?
- 二、 為什么要用原型模式?
- 三、 原型模式怎么實現?
- 四、 原型模式的應用場景
- 五、 原型模式的優點和缺點
- 六、 總結
🌟我的其他文章也講解的比較有趣😁,如果喜歡博主的講解方式,可以多多支持一下,感謝🤗!
🌟了解抽象工廠模式請看: (三)趣學設計模式 之 抽象工廠模式!
這篇文章帶你詳細認識一下設計模式中的原型模式
一、 啥是原型模式?
原型模式,說白了,就是“山寨”! 🤣 你有一個寶貝,不想自己辛辛苦苦再做一個,就找個復印機(克隆方法),咔嚓一下,復制一個一模一樣的出來!
- 對象的創建成本很高: 就像造火箭 🚀,太費勁了,不如復制一個!
- 需要創建大量相似對象: 就像克隆羊 🐑,要一大堆一樣的羊,一個個生太慢了!
- 希望隱藏對象的創建細節: 就像做菜 🍳,你只想吃,不想知道怎么做的!
二、 為什么要用原型模式?
用原型模式,好處多多:
- 省錢省力: 不用重復造輪子 🚗,直接復制,省時省力!
- 靈活多變: 想復制啥就復制啥,不用提前定好,很靈活!
- 簡單易懂: 不用管對象怎么創建的,直接復制就行,代碼更簡單!
- 避免麻煩: 不用創建一堆亂七八糟的子類,省心!
三、 原型模式怎么實現?
原型模式主要有兩種“山寨”方法:
- 淺拷貝(Shallow Copy)
- 深拷貝(Deep Copy)
實現原型模式的關鍵點
- Cloneable 接口: 就像一個“允許復制”的標簽 🏷?,貼上這個標簽,才能被復印!
- Object 類的 clone() 方法: 就像復印機的基本功能,只能復印表面信息(淺拷貝)!
- 重寫 clone() 方法: 就像升級復印機,讓它可以復印所有信息(深拷貝)!
1.淺拷貝(Shallow Copy):只復制表面! 🤏
淺拷貝就像復印身份證 🪪,你復印了一張身份證,上面的名字、地址都一樣,但是里面的芯片還是原來的那個。如果原來的身份證信息變了,復印件也會跟著變!
// 1. 定義原型接口 (Cloneable 是 Java 內置的接口)
interface Prototype extends Cloneable {Prototype clone(); // 克隆方法
}// 2. 定義具體原型類
class Sheep implements Prototype {private String name;private int age;private Address address; // 引用類型屬性public Sheep(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Address getAddress() {return address;}public void setAddress(String city) {this.address.setCity(city);}@Overridepublic Sheep clone() {try {// 淺拷貝:直接調用 Object 類的 clone() 方法return (Sheep) super.clone();} catch (CloneNotSupportedException e) {System.out.println("克隆失敗! 😭");return null;}}@Overridepublic String toString() {return "Sheep{" +"name='" + name + '\'' +", age=" + age +", address=" + address +'}';}
}// 地址類 (引用類型)
class Address {private String city;public Address(String city) {this.city = city;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}@Overridepublic String toString() {return "Address{" +"city='" + city + '\'' +'}';}
}// 3. 客戶端使用
public class Client {public static void main(String[] args) {// 創建原型對象Address address = new Address("北京");Sheep originalSheep = new Sheep("多莉", 3, address);// 克隆原型對象Sheep clonedSheep = originalSheep.clone();// 打印原始對象和克隆對象System.out.println("原始羊: " + originalSheep);System.out.println("克隆羊: " + clonedSheep);// 修改原始對象的引用類型屬性originalSheep.setAddress("上海");// 再次打印原始對象和克隆對象System.out.println("修改后的原始羊: " + originalSheep);System.out.println("修改后的克隆羊: " + clonedSheep);}
}
代碼解釋:
Prototype
:原型接口,定義了克隆方法。Sheep
:具體原型類,實現了Prototype
接口,表示羊。clone()
:克隆方法,用于創建對象的副本。Address
:地址類,作為Sheep
類的引用類型屬性。
輸出結果:
原始羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
克隆羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
修改后的原始羊: Sheep{name='多莉', age=3, address=Address{city='上海'}}
修改后的克隆羊: Sheep{name='多莉', age=3, address=Address{city='上海'}}
分析:
可以看到,修改原始羊的地址后,克隆羊的地址也跟著改變了!這是因為淺拷貝只復制了地址的“指針”,原始羊和克隆羊指向的是同一個地址,所以一個變了,另一個也跟著變!
2. 深拷貝(Deep Copy):徹底復制! 💯
深拷貝就像克隆一只完整的羊 🐑,你克隆了一只羊,它有自己的名字、年齡和地址,和原來的羊完全沒有關系。即使原來的羊死了,克隆羊還是活蹦亂跳的!
import java.io.*;class Sheep implements Prototype, Serializable { // 注意實現 Serializable 接口private String name;private int age;private Address address;public Sheep(String name, int age, Address address) {this.name = name;this.age = age;this.address = address;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}@Overridepublic Sheep clone() {try {// 使用序列化和反序列化實現深拷貝ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (Sheep) ois.readObject();} catch (Exception e) {System.out.println("克隆失敗! 😭");return null;}}@Overridepublic String toString() {return "Sheep{" +"name='" + name + '\'' +", age=" + age +", address=" + address +'}';}
}class Address implements Serializable { // 注意實現 Serializable 接口private String city;public Address(String city) {this.city = city;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}@Overridepublic String toString() {return "Address{" +"city='" + city + '\'' +'}';}
}public class Client {public static void main(String[] args) {// 創建原型對象Address address = new Address("北京");Sheep originalSheep = new Sheep("多莉", 3, address);// 克隆原型對象Sheep clonedSheep = originalSheep.clone();// 打印原始對象和克隆對象System.out.println("原始羊: " + originalSheep);System.out.println("克隆羊: " + clonedSheep);// 修改原始對象的引用類型屬性address.setCity("上海");// 再次打印原始對象和克隆對象System.out.println("修改后的原始羊: " + originalSheep);System.out.println("修改后的克隆羊: " + clonedSheep);}
}
注意: 使用序列化和反序列化實現深拷貝,需要確保對象及其所有屬性都實現了 Serializable
接口。
輸出結果:
原始羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
克隆羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
修改后的原始羊: Sheep{name='多莉', age=3, address=Address{city='上海'}}
修改后的克隆羊: Sheep{name='多莉', age=3, address=Address{city='北京'}}
分析:
可以看到,修改原始羊的地址后,克隆羊的地址沒有改變!這是因為深拷貝復制了地址的所有信息,原始羊和克隆羊擁有不同的地址,所以一個變了,另一個不會受到影響!
四、 原型模式的應用場景
- 游戲開發: 游戲里的怪物 👾,不用一個個設計,復制幾個改改屬性就行!
- 文檔編輯器: 文檔的模板 📄,復制一份改改,就能生成新的文檔!
- 圖形編輯器: 圖形對象 🖼?,復制一份改改顏色、大小,就能得到新的圖形!
- 配置管理: 軟件的配置 ??,復制一份改改,就能適應不同的環境!
- 報表生成: 報表的模板 📊,復制一份改改數據,就能生成不同的報表!
五、 原型模式的優點和缺點
優點:
- 省錢省力: 不用重新創建對象,直接復制,就像復制粘貼一樣簡單,大大提高了效率!
- 隱藏秘密: 客戶端不用知道對象是怎么創建的,只需要知道怎么復制就行,保護了對象的內部結構!
- 靈活應變: 可以在運行時動態地復制對象,想復制啥就復制啥,非常靈活!
- 減少麻煩: 不用創建一堆子類,避免了代碼的膨脹,讓代碼更簡潔!
缺點:
- 復制很麻煩: 特別是深拷貝,要復制對象的所有信息,就像搬家一樣,很麻煩!
- 破壞隱私: 為了復制對象,可能需要訪問對象的私有屬性,就像偷看別人的日記一樣,破壞了對象的封裝性!
- 需要貼標簽: 在 Java 中,需要實現
Cloneable
接口才能被復制,就像需要貼上“允許復制”的標簽,有點麻煩! - 可能出錯: 有時候復制操作可能不會執行構造函數,就像克隆羊沒有靈魂一樣,可能會導致對象的狀態不正確!
六、 總結
- 原型模式就是“山寨”大法,通過復制現有對象來創建新對象!
- 適用于需要大量相似對象、對象創建成本高、需要隱藏對象創建細節的場景!
- 有兩種“山寨”方法:淺拷貝和深拷貝!
- 淺拷貝只復制表面信息,深拷貝復制所有信息!
- 要根據實際情況選擇合適的“山寨”方法!
- 使用原型模式需要注意一些問題,比如破壞封裝性、可能出錯等等!
希望這篇文章能讓你徹底理解原型模式! 👍
看完請看:(五)趣學設計模式 之 建造者模式!