「設計模式自習室」原型模式

「設計模式自習室」原型模式

前言

《設計模式自習室》系列,顧名思義,本系列文章帶你溫習常見的設計模式。主要內容有:

  • 該設計模式的詳細介紹,包括: 引子,意圖(大白話解釋) 類圖,時序圖(理論規範)
  • 該模式的代碼示例:熟悉該模式的代碼長什麼樣子
  • 該模式的優缺點:不可以濫用模式
  • 該模式的實際使用案例:瞭解它在哪些重要的源碼中出現過

該系列會逐步更新於我的博客和公眾號(博客見文章底部)

也希望各位觀眾老爺能夠關注我的個人公眾號:後端技術漫談,不會錯過精彩好看的文章。

系列文章回顧

  • 【設計模式自習室】開篇:為什麼我們要用設計模式?
  • 【設計模式自習室】建造者模式

原型模式 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

 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;     } } /<code>
<code>

class

 

Book

 

implements

 

Cloneable

 

{     

private

 String title;     

private

 

int

 pageNum;     

private

 Author author;     

public

 Book 

clone

()

 

{         Book book = 

null

;         

try

 {             book = (Book) 

super

.clone();         } 

catch

 (CloneNotSupportedException e) {                          e.printStackTrace();         }         

return

 book;     }     

public

 String 

getTitle

()

 

{         

return

 title;     }     

public

 

void

 

setTitle

(String title)

 

{         

this

.title = title;     }     

public

 

int

 

getPageNum

()

 

{         

return

 pageNum;     }     

public

 

void

 

setPageNum

(

int

 pageNum)

 

{         

this

.pageNum = pageNum;     }     

public

 Author 

getAuthor

()

 

{         

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

  Object 

deepClone

()

 

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

 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;     } } /<code>
<code>

class

 

Book

 

implements

 

Serializable

 

{     

private

 String title;     

private

 

int

 pageNum;     

private

 Author author;     

public

 Book 

deepClone

()

 

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

 String 

getTitle

()

 

{         

return

 title;     }     

public

 

void

 

setTitle

(String title)

 

{         

this

.title = title;     }     

public

 

int

 

getPageNum

()

 

{         

return

 pageNum;     }     

public

 

void

 

setPageNum

(

int

 pageNum)

 

{         

this

.pageNum = pageNum;     }     

public

 Author 

getAuthor

()

 

{         

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

如果文章對你有幫助,不妨收藏,投幣,轉發,在看起來~


分享到:


相關文章: