設計模式:揭秘Java原型模式——讓復雜對象的創建不再復雜

原型模式

原型模式介紹

定義: 原型模式(Prototype Design Pattern)用一個已經創建的實例作為原型,通過復制該原型對象來創建一個和原型對象相同的新對象。

西游記中的孫悟空,拔毛變小猴,孫悟空這種根據自己的形狀復制出多個身外化身的技巧,在面向對象軟件設計領域被稱為原型模式。孫悟空就是原型對象。

在這里插入圖片描述
原型模式主要解決的問題

  • 如果創建對象的成本比較大,比如對象中的數據是經過復雜計算才能得到,或者需要從RPC接口或者數據庫等比較慢的IO中獲取,這種情況我們就可以使用原型模式,從其他已有的對象中進行拷貝,而不是每次都創建新對象,進行一些耗時的操作。

原型模式原理

原型模式包含如下幾種角色。

  • 抽象原型類(Prototype):它是聲明克隆方法的接口,是所有具體原型類的公共父類,它可以是抽象類也可以是接口;
  • 具體原型類(ConcretePrototype):實現在抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個克隆對象;
  • 客戶類(Client):在客戶類中,讓一個原型對象克隆自身從而創建一個新的對象。由于客戶類針對抽象原型類Prototype編程,因此用戶可以根據需要選擇具體原型類。系統具有較好的擴展性,增加或者替換具體原型類都比較方便。

在這里插入圖片描述

深克隆與淺克隆

根據在復制原型對象的同時是否復制包含在原型對象中引用類型的成員變量 這個條件,原型模式的克隆機制分為兩種,即淺克隆(Shallow Clone)和深克隆(Deep Clone)。

1) 什么是淺克隆

被復制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象(克隆對象與原型對象共享引用數據類型變量)。
在這里插入圖片描述

2) 什么是深克隆

除去那些引用其他對象的變量,被復制對象的所有變量都含有與原來的對象相同的值。那些引用其他對象的變量將指向被復制過的新對象,而不再是原有的那些被引用的對象。換言之,深復制把要復制的對象所引用的對象都復制了一遍。
在這里插入圖片描述

Java中的Object類中提供了 clone() 方法來實現淺克隆。需要注意的是要想實現克隆的 Java 類必須實現一個標識接口 Cloneable ,來表示這個Java類支持被復制。

Cloneable接口是上面的類圖中的抽象原型類,而實現了Cloneable接口的子實現類就是具體的原型類。代碼如下:

3) 淺克隆代碼實現:

public class ConcretePrototype implements Cloneable {public ConcretePrototype() {System.out.println("具體的原型對象創建完成!");}@Overrideprotected ConcretePrototype clone() throws CloneNotSupportedException {System.out.println("具體的原型對象復制成功!");return (ConcretePrototype)super.clone();}
}

測試

    @Testpublic void test01() throws CloneNotSupportedException {ConcretePrototype c1 = new ConcretePrototype();ConcretePrototype c2 = c1.clone();System.out.println("對象c1和c2是同一個對象?" + (c1 == c2));}

4) 深克隆代碼實現

在ConcretePrototype類中添加一個對象屬性為Person類型

public class ConcretePrototype implements Cloneable {private Person person;public Person getPerson() {return person;}public void setPerson(Person person) {this.person = person;}void show(){System.out.println("嫌疑人姓名: " +person.getName());}public ConcretePrototype() {System.out.println("具體的原型對象創建完成!");}@Overrideprotected ConcretePrototype clone() throws CloneNotSupportedException {System.out.println("具體的原型對象復制成功!");return (ConcretePrototype)super.clone();}}public class Person {private String name;public Person() {}public Person(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

測試

    @Testpublic void test02() throws CloneNotSupportedException {ConcretePrototype c1 = new ConcretePrototype();Person p1 = new Person();c1.setPerson(p1);//復制c1ConcretePrototype c2 = c1.clone();//獲取復制對象c2中的Person對象Person p2 = c2.getPerson();p2.setName("峰哥");//判斷p1與p2是否是同一對象System.out.println("p1和p2是同一個對象?" + (p1 == p2));c1.show();c2.show();}

打印結果

在這里插入圖片描述

說明: p1與p2是同一對象,這是淺克隆的效果,也就是對具體原型類中的引用數據類型的屬性進行引用的復制。

如果有需求場景中不允許共享同一對象,那么就需要使用深拷貝,如果想要進行深拷貝需要使用到對象序列化流 (對象序列化之后,再進行反序列化獲取到的是不同對象)。 代碼如下:

    @Testpublic void test03() throws Exception {ConcretePrototype c1 = new ConcretePrototype();Person p1 = new Person("峰哥");c1.setPerson(p1);//創建對象序列化輸出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c.txt"));//將c1對象寫到文件中oos.writeObject(c1);oos.close();//創建對象序列化輸入流ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c.txt"));//讀取對象ConcretePrototype c2 = (ConcretePrototype) ois.readObject();Person p2 = c2.getPerson();p2.setName("凡哥");//判斷p1與p2是否是同一個對象System.out.println("p1和p2是同一個對象?" + (p1 == p2));c1.show();c2.show();}

打印結果:

在這里插入圖片描述

注意:ConcretePrototype類和Person類必須實現Serializable接口,否則會拋NotSerializableException異常。

其實現在不推薦大家用Cloneable接口,實現比較麻煩,現在借助Apache Commons或者springframework可以直接實現:

  • 淺克隆:BeanUtils.cloneBean(Object obj);BeanUtils.copyProperties(S,T);
  • 深克隆:SerializationUtils.clone(T object);

BeanUtils是利用反射原理獲得所有類可見的屬性和方法,然后復制到target類。
SerializationUtils.clone()就是使用我們的前面講的序列化實現深克隆,當然你要把要克隆的類實現Serialization接口。

4.5.4 原型模式應用實例

模擬某銀行電子賬單系統的廣告信發送功能,廣告信的發送都是有一個模板的,從數據庫查出客戶的信息,然后放到模板中生成一份完整的郵件,然后交給發送機進行發送處理。
在這里插入圖片描述

發送廣告信郵件UML類圖

在這里插入圖片描述

代碼實現

  • 廣告模板代碼
/*** 廣告信模板代碼**/
public class AdvTemplate {//廣告信名稱private String advSubject = "xx銀行本月還款達標,可抽iPhone 13等好禮!";//廣告信內容private String advContext = "達標用戶請在2022年3月1日到2022年3月30參與抽獎......";public String getAdvSubject() {return this.advSubject;}public String getAdvContext() {return this.advContext;}
}
  • 郵件類代碼
package com.mashibing.example01;/*** 郵件類**/
public class Mail {//收件人private String receiver;//郵件名稱private String subject;//稱謂private String appellation;//郵件內容private String context;//郵件尾部, 一般是"xxx版權所有"等信息private String tail;//構造函數public Mail(AdvTemplate advTemplate) {this.context = advTemplate.getAdvContext();this.subject = advTemplate.getAdvSubject();}public String getReceiver() {return receiver;}public void setReceiver(String receiver) {this.receiver = receiver;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public String getAppellation() {return appellation;}public void setAppellation(String appellation) {this.appellation = appellation;}public String getContext() {return context;}public void setContext(String context) {this.context = context;}public String getTail() {return tail;}public void setTail(String tail) {this.tail = tail;}
}
  • 客戶類
/*** 業務場景類**/
public class Client {//發送信息的是數量,這個值可以從數據庫獲取private static int MAX_COUNT = 6;//發送郵件public static void sendMail(Mail mail){System.out.println("標題: " + mail.getSubject() + "\t收件人: " + mail.getReceiver()+ "\t..發送成功!");}public static void main(String[] args) {//模擬郵件發送int i = 0;//把模板定義出來,數據是從數據庫獲取的Mail mail = new Mail(new AdvTemplate());mail.setTail("xxx銀行版權所有");while(i < MAX_COUNT){//下面是每封郵件不同的地方mail.setAppellation(" 先生 (女士)");Random random = new Random();int num = random.nextInt(9999999);mail.setReceiver(num+"@"+"liuliuqiu.com");//發送 郵件sendMail(mail);i++;}}
}
  • 運行結果
    在這里插入圖片描述

上面的代碼存在的問題:

  • 發送郵件需要重復創建Mail類對象,而且Mail類的不同對象之間差別非常小,這樣重復的創建操作十分的浪費資源;
  • 這種情況我們就可以使用原型模式,從其他已有的對象中進行拷貝,而不是每次都創建新對象,進行一些耗時的操作。

代碼重構

  • Mail類
/*** 郵件類 實現Cloneable接口,表示該類的實例可以被復制* @author spikeCong* @date 2022/9/20**/
public class Mail implements Cloneable{//收件人private String receiver;//郵件名稱private String subject;//稱謂private String appellation;//郵件內容private String context;//郵件尾部, 一般是"xxx版權所有"等信息private String tail;//構造函數public Mail(AdvTemplate advTemplate) {this.context = advTemplate.getAdvContext();this.subject = advTemplate.getAdvSubject();}public String getReceiver() {return receiver;}public void setReceiver(String receiver) {this.receiver = receiver;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public String getAppellation() {return appellation;}public void setAppellation(String appellation) {this.appellation = appellation;}public String getContext() {return context;}public void setContext(String context) {this.context = context;}public String getTail() {return tail;}public void setTail(String tail) {this.tail = tail;}@Overridepublic Mail clone(){Mail mail = null;try {mail = (Mail)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return mail;}
}
  • Client類
/*** 業務場景類* @author spikeCong* @date 2022/9/20**/
public class Client {//發送信息的是數量,這個值可以從數據庫獲取private static int MAX_COUNT = 6;//發送郵件public static void sendMail(Mail mail){System.out.println("標題: " + mail.getSubject() + "\t收件人: " + mail.getReceiver()+ "\t..發送成功!");}public static void main(String[] args) {//模擬郵件發送int i = 0;//把模板定義出來,數據是從數據庫獲取的Mail mail = new Mail(new AdvTemplate());mail.setTail("xxx銀行版權所有");while(i < MAX_COUNT){//下面是每封郵件不同的地方Mail cloneMail = mail.clone();cloneMail.setAppellation(" 先生 (女士)");Random random = new Random();int num = random.nextInt(9999999);cloneMail.setReceiver(num+"@"+"liuliuqiu.com");//發送 郵件sendMail(cloneMail);i++;}}
}

4.5.5 原型模式總結

原型模式的優點

  1. 當創建新的對象實例較為復雜時,使用原型模式可以簡化對象的創建過程,通過復制一個已有實例可以提高新實例的創建效率。

    比如,在 AI 系統中,我們經常需要頻繁使用大量不同分類的數據模型文件,在對這一類文件建立對象模型時,不僅會長時間占用 IO 讀寫資源,還會消耗大量 CPU 運算資源,如果頻繁創建模型對象,就會很容易造成服務器 CPU 被打滿而導致系統宕機。通過原型模式我們可以很容易地解決這個問題,當我們完成對象的第一次初始化后,新創建的對象便使用對象拷貝(在內存中進行二進制流的拷貝),雖然拷貝也會消耗一定資源,但是相比初始化的外部讀寫和運算來說,內存拷貝消耗會小很多,而且速度快很多。

  2. 原型模式提供了簡化的創建結構,工廠方法模式常常需要有一個與產品類等級結構相同的工廠等級結構(具體工廠對應具體產品),而原型模式就不需要這樣,原型模式的產品復制是通過封裝在原型類中的克隆方法實現的,無須專門的工廠類來創建產品。

  3. 可以使用深克隆的方式保存對象狀態,使用原型模式將對象復制一份并將其狀態保存起來,以便在需要的時候使用,比如恢復到某一歷史狀態,可以輔助實現撤銷操作。

    在某些需要保存歷史狀態的場景中,比如,聊天消息、上線發布流程、需要撤銷操作的程序等,原型模式能快速地復制現有對象的狀態并留存副本,方便快速地回滾到上一次保存或最初的狀態,避免因網絡延遲、誤操作等原因而造成數據的不可恢復。

原型模式缺點

  • 需要為每一個類配備一個克隆方法,而且該克隆方法位于一個類的內部,當對已有的類進行改造時需要修改源代碼,違背了開閉原則。

使用場景

原型模式常見的使用場景有以下六種。

  • 資源優化場景。也就是當進行對象初始化需要使用很多外部資源時,比如,IO 資源、數據文件、CPU、網絡和內存等。

  • 復雜的依賴場景。 比如,F 對象的創建依賴 A,A 又依賴 B,B 又依賴 C……于是創建過程是一連串對象的 get 和 set。

  • 性能和安全要求的場景。 比如,同一個用戶在一個會話周期里,可能會反復登錄平臺或使用某些受限的功能,每一次訪問請求都會訪問授權服務器進行授權,但如果每次都通過 new 產生一個對象會非常煩瑣,這時則可以使用原型模式。

  • 同一個對象可能被多個修改者使用的場景。 比如,一個商品對象需要提供給物流、會員、訂單等多個服務訪問,而且各個調用者可能都需要修改其值時,就可以考慮使用原型模式。

  • 需要保存原始對象狀態的場景。 比如,記錄歷史操作的場景中,就可以通過原型模式快速保存記錄。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/86413.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/86413.shtml
英文地址,請注明出處:http://en.pswp.cn/web/86413.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Go語言-文件操作

基本介紹 文件是數據源&#xff0c;數據庫也是一種特殊的文件。 Go語言中os.File結構體封裝了文件的相關操作。 打開和關閉文件 -----打開文件----- file, err : os.Open("D:/111.txt") if err ! nil{fmt.Println("err ", err) }此時file就是一個指針&…

【電力物聯網】云–邊協同介紹

(??? )&#xff0c;Hello&#xff0c;我是祐言QAQ我的博客主頁&#xff1a;C/C語言&#xff0c;數據結構&#xff0c;Linux基礎&#xff0c;ARM開發板&#xff0c;網絡編程等領域UP&#x1f30d;快上&#x1f698;&#xff0c;一起學習&#xff0c;讓我們成為一個強大的技術…

《深入解析 C#(第 4 版)》推薦

《深入解析 C#&#xff08;第 4 版&#xff09;》推薦 在 C# 語言不斷演進的技術浪潮中&#xff0c;《深入解析 C#&#xff08;第 4 版&#xff09;》猶如一座燈塔&#xff0c;為開發者照亮探索的道路。無論是經驗豐富的老程序員&#xff0c;還是初入 C# 領域的新手&#xff0c…

【網絡】Linux 內核優化實戰 - net.core.netdev_max_backlog

目錄 Linux 內核參數 net.core.netdev_max_backlog 詳解一、參數概述二、參數功能與作用2.1 核心功能2.2 網絡數據包處理流程 三、查看當前參數值3.1 通過 sysctl 命令3.2 直接讀取 /proc/sys 文件 四、修改參數值4.1 臨時修改&#xff08;立即生效&#xff0c;重啟后失效&…

Nuitka 打包Python程序

文章目錄 Nuitka 打包Python程序&#x1f680; **一、Nuitka 核心優勢**?? **二、環境準備&#xff08;Windows 示例&#xff09;**&#x1f4e6; **三、基礎打包命令****單文件腳本打包****帶第三方庫的項目** &#x1f6e0;? **四、高級配置選項****示例&#xff1a;完整命…

自動獲取文件的內存大小怎么設置?批量獲取文件名和內存大小到Excel中的方法

在對重要數據進行備份或遷移操作前&#xff0c;為確保備份全面無遺漏&#xff0c;且合理規劃目標存儲設備的空間&#xff0c;會將文件名和內存提取到 Excel。比如&#xff0c;某個部門要將舊電腦中的文件遷移到新服務器&#xff0c;提前整理文件信息&#xff0c;能清晰知道所需…

創建型設計模式——單例模式

單例設計模式 什么是創建型設計模式有哪些創建型設計模式 單例設計模式實現方法餓漢式單例懶漢式單例實現方法 CSDN——C單例模式詳解 單例設計模式是一種創建型設計模式 什么是創建型設計模式 創建型設計模式&#xff0c;就是通過控制對象的創建方式來解決設計問題。 有哪…

html 照片環 - 圖片的動態3D環繞

html 照片環 - 圖片的動態3D環繞 引言一、源碼二、圖轉base64參考鏈接 引言 效果展示&#xff1a; 一、源碼 原始圖片的base64編碼字符太多了&#xff0c;博客放不下&#xff0c;將圖片縮小后的加入html的源碼如下&#xff1a; <!DOCTYPE html> <html><hea…

ADIOS2 介紹與使用指南

文章目錄 ADIOS2 介紹與使用指南什么是ADIOS2?ADIOS2 的主要特點ADIOS2 核心概念ADIOS2 安裝Linux 系統安裝Windows 安裝 ADIOS2 基本使用C 示例Python 示例 ADIOS2 高級特性并行I/O流模式 ADIOS2 引擎類型性能優化建議總結 ADIOS2 介紹與使用指南 什么是ADIOS2? ADIOS2(Ad…

網絡安全 vs 信息安全的本質解析:數據盾牌與網絡防線的辯證關系關系

在數字化生存的今天&#xff0c;每一次手機支付、每一份云端文檔、每一條醫療記錄的背后&#xff0c;都矗立著這兩座安全堡壘。理解它們的協同邏輯&#xff0c;不僅是技術從業者的必修課&#xff0c;更是企業構建數字防護體系的底層認知 —— 畢竟當勒索軟件同時切斷 "護城…

ping-pong操作

常見不匹配的原因 瞬時數據率的差異&#xff1b; 數據順序的差異&#xff1b; 對比維度PipelineFIFOPing-Pong邏輯復制結構類型時序分級推進&#xff08;寄存器鏈&#xff09;環形隊列&#xff08;緩沖區&#xff09;雙緩沖區&#xff08;輪換使用&#xff09;功能塊并行&am…

21.合并兩個有序鏈表

將兩個升序鏈表合并為一個新的 升序 鏈表并返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。 思路&#xff1a;這里使用的主要數據結構是單鏈表。該算法采用經典的雙指針技術來合并列表。 A dummy node is created; this node does not hold any meaningful value b…

vue3中簡單易懂說明nextTick的使用

nextTick(): 等待下一次 DOM 更新刷新的工具方法 重點解釋: 當你在 Vue 中更改響應式狀態時&#xff0c;最終的 DOM 更新并不是同步生效的&#xff0c;而是由 Vue 將它們緩存在一個隊列中&#xff0c;直到下一個“tick”才一起執行。這樣是為了確保每個組件無論發生多少狀態改變…

gRPC 相關介紹

介紹 依賴兩大技術 HTTP/2 作為傳輸協議 gRPC 底層用 HTTP/2&#xff0c;它支持&#xff1a; 多路復用&#xff08;在一條 TCP 連接中并行傳輸多個請求和響應&#xff09;二進制傳輸&#xff08;更緊湊、高效&#xff09;流式傳輸&#xff08;客戶端流、服務端流、雙向流&…

PyTorch 模型鏡像下載與安裝指南

在國內&#xff0c;由于網絡限制&#xff0c;直接從 PyTorch 官方源下載可能會遇到速度慢或無法訪問的問題。為了解決這一問題&#xff0c;可以使用國內鏡像源來加速下載和安裝 PyTorch。 文章目錄 安裝指定版本的 PyTorch&#xff08;以 CUDA 11.8 為例&#xff09;安裝 CPU 版…

2025年SVN學習價值分析

?? 一、SVN的現狀與應用場景分析 仍在特定領域發揮作用 傳統企業維護場景&#xff1a;在金融、電信、政府等采用集中式開發流程的機構中&#xff0c;許多遺留系統仍使用SVN管理。這些系統往往體量龐大、架構穩定&#xff0c;遷移成本高&#xff0c;因此SVN短期內不會被完全替…

JavaScript中的10種排序算法:從入門到精通

作為前端開發者&#xff0c;排序算法是我們必須掌握的基礎知識。無論是在面試中&#xff0c;還是在實際開發中處理數據展示時&#xff0c;排序都是一個常見需求。今天&#xff0c;我將用通俗易懂的方式&#xff0c;帶你了解JavaScript中最常見的10種排序算法。 1. 冒泡排序 - …

【微信小程序】6、SpringBoot整合WxJava獲取用戶手機號

1、手機號快速驗證組件 手機號快速驗證組件 旨在幫助開發者向用戶發起手機號申請&#xff0c;并且必須經過用戶同意后&#xff0c;開發者才可獲得由平臺驗證后的手機號&#xff0c;進而為用戶提供相應服務。 該能力與手機號實時驗證組件的區別為&#xff1a; 手機號快速驗證…

redis8.0新特性:原生JSON支持詳解

文章目錄 一、寫在前面二、使用1、基本命令&#xff08;1&#xff09;JSON.SET 設置 JSON 值&#xff08;2&#xff09;JSON.GET 獲取 JSON 值&#xff08;3&#xff09;JSON.DEL 刪除 JSON 值&#xff08;4&#xff09;JSON.MGET 批量獲取&#xff08;5&#xff09;JSON.MSET …

QT網絡調試助手開發全指南,軟件設計圖預研,后續文檔跟進補充

網絡調試助手 1 TCP網絡調試助手 1.1 項目概述 網絡相關的一些基礎概念學習QTcpServer 學習QTcpClient 學習TextEdit特定位置輸入文字顏色學習網絡通信相關知識點 復習鞏固之前UI控件 程序運行如下圖所示 1.2 開發流程 1.3 QTtcp 服務器的關鍵流程 工程建立&#xff0c;需要在…