前言
《設計模式自習室》系列,顧名思義,本系列文章帶你溫習常見的設計模式。主要內容有:
- 該設計模式的詳細介紹,包括: 引子,意圖(大白話解釋) 類圖,時序圖(理論規範)
- 該模式的代碼示例:熟悉該模式的代碼長什麼樣子
- 該模式的優缺點:不可以濫用模式
- 該模式的實際使用案例:瞭解它在哪些重要的源碼中出現過
該系列會逐步更新於我的博客和公眾號(博客見文章底部)
也希望各位觀眾老爺能夠關注我的個人公眾號:後端技術漫談,不會錯過精彩好看的文章。
系列文章回顧
- 【設計模式自習室】開篇:為什麼我們要用設計模式?
- 【設計模式自習室】建造者模式
原型模式 Prototype
引子
還記得深克隆和淺克隆的區別嗎?其實這裡說的克隆,就是原型模式。
原型模式要求對象實現一個可以克隆自身的接口(類型)。這樣一來,通過原型實例創建新的對象。
原型模式也屬於創建型模式。
意圖
原型模式有兩種表現形式:
- 簡單形式
- 登記形式
他們的區別在於:第二種登記模式中,多了一個原型管理器(PrototypeManager)角色,該角色的作用是:創建具體原型類的對象,並記錄每一個被創建的對象。
如果需要創建的原型對象數目較少而且比較固定的話,可以採取簡單形式。在這種情況下,原型對象的引用可以由客戶端自己保存。
否則,你可以使用登記形式。原型管理器的作用:
在登記形式下,客戶端不保存對原型對象的引用,這個任務被交給原型管理器角色。在克隆一個對象之前,客戶端可以查看管理員對象是否已經有一個滿足要求的原型對象。如果有,可以從原型管理器角色中取得這個對象引用;如果沒有,客戶端就需要自行復制此原型對象。
類圖
簡單形式
- 客戶(Client):客戶類提出創建對象的請求;
- 抽象原型(Prototype):這是一個抽象角色,通常是一個Java接口或者抽象類。此角色定義了的具體原型類所需的實現的方法。
- 具體原型(Concrete Prototype):此角色需要實現抽象原型角色要求的克隆相關的接口。
登記形式
多出了:
- 原型管理器(PrototypeManager):客戶端client直接調用原型管理器
時序圖
無
使用舉例和實際使用場景舉例
本文將使用舉例和實際使用場景舉例放在一起來討論,是由於原型模式最典型的例子就是Java的深克隆和淺克隆 (clone方法),我們直接使用深克隆和淺克隆的代碼來熟悉原型模式。
clone()方法將對象複製了一份並返還給調用者。所謂“複製”的含義與clone()方法是怎麼實現的。一般而言,clone()方法滿足以下的描述:
<code>(1
)對任何的對象x,都有:x.clone
()!=x。換言之,克隆對象與原對象不是同一個對象。 (2
)對任何的對象x,都有:x.clone
().getClass() == x.getClass(),換言之,克隆對象與原對象的類型一樣。 (3
)如果對象x的equals()方法定義其恰當的話,那麼x.clone
().equals(x)應當成立的。 /<code>
在JAVA語言的API中,凡是提供了clone()方法的類,都滿足上面的這些條件。JAVA語言的設計師在設計自己的clone()方法時,也應當遵守著三個條件。一般來說,上面的三個條件中的前兩個是必需的,而第三個是可選的。
淺複製 clone()
當進淺複製時,clone函數返回的是一個引用,指向的是新的clone出來的對象,此對象與原對象分別佔用不同的堆空間。同時,複製出來的對象具有與原對象一致的狀態。
此處對象一致的狀態是指:複製出的對象與原對象中的屬性值完全相等==。
代碼示例:
我們複製一本書
1.定義Book類和Author類:
<code>class
Author
{private
String name;private
int
age;public
StringgetName
()
{return
name; }public
void
setName
(String name)
{this
.name = name; }public
int
getAge
()
{return
age; }public
void
setAge
(
int
age) {this
.age = age; } } /<code>
<code>class
Book
implements
Cloneable
{private
String title;private
int
pageNum;private
Author author;public
Bookclone
()
{ Book book =null
;try
{ book = (Book)super
.clone(); }catch
(CloneNotSupportedException e) { e.printStackTrace(); }return
book; }public
StringgetTitle
()
{return
title; }public
void
setTitle
(String title)
{this
.title = title; }public
int
getPageNum
()
{return
pageNum; }public
void
setPageNum
(
int
pageNum) {this
.pageNum = pageNum; }public
AuthorgetAuthor
()
{return
author; }public
void
setAuthor
(Author author)
{this
.author = author; } } /<code>
2.測試:
<code>package com.qqyumidi;public
class
PrototypeTest
{public
static
void
main
(String[] args
) { Book book1 =new
Book(); Author author =new
Author(); author.setName("corn"
); author.setAge(100
); book1.setAuthor(author); book1.setTitle("好記性不如爛博客"
); book1.setPageNum(230
); Book book2 = book1.clone(); System.out
.println(book1 == book2); System.out
.println(book1.getPageNum() == book2.getPageNum()); System.out
.println(book1.getTitle() == book2.getTitle()); System.out
.println(book1.getAuthor() == book2.getAuthor()); } } /<code>
雖然複製出來的對象重新在堆上開闢了內存空間,但是,對象中各屬性確保持相等。對於基本數據類型很好理解,但對於引用數據類型來說,則意味著此引用類型的屬性所指向的對象本身是相同的, 並沒有重新開闢內存空間存儲。換句話說,引用類型的屬性所指向的對象並沒有複製。
由此,我們將其稱之為淺複製。
而當複製後的對象的引用類型的屬性所指向的對象也重新得以複製,此時,稱之為深複製。
深複製 deepclone()
Java中的深複製一般是通過對象的序列化和反序列化得以實現。序列化時,需要實現Serializable接口。
從輸出結果中可以看出,深複製不僅在堆內存上開闢了空間以存儲複製出的對象,甚至連對象中的引用類型的屬性所指向的對象也得以複製,重新開闢了堆空間存儲。
深複製的兩種實現方式
在Java語音中,想要實現深度拷貝其實有兩種方式:
- 通過序列化
- 重寫clone,調用子成員的clone方法
代碼示例:通過 序列化 實現深複製
<code>public
ObjectdeepClone
()
throws
IOException, ClassNotFoundException{ ByteArrayOutputStream bos =new
ByteArrayOutputStream(); ObjectOutputStream oos =new
ObjectOutputStream(bos); oos.writeObject(this
); ByteArrayInputStream bis =new
ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois =new
ObjectInputStream(bis);return
ois.readObject(); } /<code>
代碼示例:通過 重寫clone 實現深複製
1.定義Book類和Author類(注意:不僅Book類需要實現Serializable接口,Author同樣也需要實現Serializable接口!!):
<code>class
Author
implements
Serializable
{private
String name;private
int
age;public
StringgetName
()
{return
name; }public
void
setName
(String name)
{this
.name = name; }public
int
getAge
()
{return
age; }public
void
setAge
(
int
age) {this
.age = age; } } /<code>
<code>class
Book
implements
Serializable
{private
String title;private
int
pageNum;private
Author author;public
BookdeepClone
()
throws
IOException, ClassNotFoundException{ ByteArrayOutputStream bos =new
ByteArrayOutputStream(); ObjectOutputStream oos =new
ObjectOutputStream(bos); oos.writeObject(this
); ByteArrayInputStream bis =new
ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois =new
ObjectInputStream(bis);return
(Book) ois.readObject(); }public
StringgetTitle
()
{return
title; }public
void
setTitle
(String title)
{this
.title = title; }public
int
getPageNum
()
{return
pageNum; }public
void
setPageNum
(
int
pageNum) {this
.pageNum = pageNum; }public
AuthorgetAuthor
()
{return
author; }public
void
setAuthor
(Author author)
{this
.author = author; } } /<code>
2.測試:
<code>public
class
PrototypeTest
{public
static
void
main
(String[] args
) throws ClassNotFoundException, IOException { Book book1 =new
Book(); Author author =new
Author(); author.setName("corn"
); author.setAge(100
); book1.setAuthor(author); book1.setTitle("好記性不如爛博客"
); book1.setPageNum(230
); Book book2 = book1.deepClone(); System.out
.println(book1 == book2); System.out
.println(book1.getPageNum() == book2.getPageNum()); System.out
.println(book1.getTitle() == book2.getTitle()); System.out
.println(book1.getAuthor() == book2.getAuthor()); } } /<code>
深複製不僅在堆內存上開闢了空間以存儲複製出的對象,甚至連對象中的引用類型的屬性所指向的對象也得以複製,重新開闢了堆空間存儲。
該模式的優缺點
優點
原型模式允許在運行時動態改變具體的實現類型。原型模式可以在運行期間,有客戶來註冊符合原型接口的實現類型,也可以動態的改變具體的實現類型,看起來接口沒有任何變化,但是其實運行的已經是另外一個類實體了。因為克隆一個原型對象就類似於實例化一個類。
缺點
缺點就是…你需要自己寫一個clone方法
參考
https://juejin.im/post/5b48bec15188251b3d79bf9c
https://www.cnblogs.com/java-my-life/archive/2012/04/11/2439387.html
https://www.jianshu.com/p/b0e13717b6d9
《Head First設計模式》
關注我
我是一名後端開發工程師。
主要關注後端開發,數據安全,爬蟲,物聯網,邊緣計算等方向,歡迎交流。
各大平臺都可以找到我
- 微信公眾號:後端技術漫談
- Github:@qqxx6661
- CSDN:@Rude3Knife
- 知乎:@Zhendong
- 簡書:@蠻三刀把刀
- 掘金:@蠻三刀把刀
原創博客主要內容
- Java面試知識點複習全手冊
- 設計模式/數據結構 自習室
- Leetcode/劍指offer 算法題解析
- SpringBoot/SpringCloud菜鳥入門實戰系列
- 爬蟲相關技術文章
- 後端開發相關技術文章
- 逸聞趣事/好書分享/個人興趣
個人公眾號:後端技術漫談
公眾號:後端技術漫談.jpg
如果文章對你有幫助,不妨收藏,投幣,轉發,在看起來~