摘要:備忘錄模式常常與命令模式和迭代子模式一同使用。自述歷史所謂自述歷史模式實際上就是備忘錄模式的一個變種。在備忘錄模式中,發起人角色負責人角色和備忘錄角色都是獨立的角色。
概述備忘錄模式(Memento Pattern)屬于行為型模式的一種,在不破壞封裝特性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。這樣就可以將該對象恢復到原先保存的狀態。
備忘錄模式又叫做快照模式(Snapshot Pattern),一個用來存儲另外一個對象內部狀態的快照的對象。
備忘錄模式的用意是在不破壞封裝的條件下,將一個對象的狀態捕捉(Capture)住,并外部化,存儲起來,從而可以在將來合適的時候把這個對象還原到存儲起來的狀態。備忘錄模式常常與命令模式和迭代子模式一同使用。
案例前言:備忘錄模式按照備忘錄角色的形態不同,分為白箱實現與黑箱實現,兩種模式與備忘錄角色提供的接口模式有關;
引入兩個定義,備忘錄有兩個等效的接口:
窄接口:負責人(Caretaker)對象(和其他除發起人對象之外的任何對象)看到的是備忘錄的窄接口(narrow interface),這個窄接口只允許它把備忘錄對象傳給其他的對象。
寬接口:與負責人對象看到的窄接口相反的是,發起人對象可以看到一個寬接口(wide interface),這個寬接口允許它讀取所有的數據,以便根據這些數據恢復這個發起人對象的內部狀態。
白箱實現角色組成
Memento(備忘錄角色): 負責存儲原發器對象的內部狀態,但是具體需要存儲哪些數據是由原發器對象來決定的,在需要的時候提供原發器需要的內部狀態。PS:這里可以存儲狀態。
Originator(發起人(原發器)角色): 記錄當前時刻的內部狀態,負責定義哪些屬于備份范圍的狀態,負責創建和恢復備忘錄數據。
Caretaker(備忘錄負責人(管理者)角色): 對備忘錄對象進行管理,但是不能對備忘錄對象的內容進行操作或檢查。
備忘錄角色對任何對象都提供公共的訪問,內部所存儲的狀態對對象公開,即為白箱實現。白箱實現發起人和負責人提供相同接口,使得負責人可以訪問備忘錄全部內容,并不安全。白箱實現對備忘錄內容的保護靠的是程序員的自律,實現也很簡單。
UML結構圖
1.創建一個備忘錄角色備忘錄,內部定義了一個變量用來區分當前對象狀態
public class Memento { private String state; public Memento(String state) { this.state = state; } public String getState() { return this.state; } public void setState(String state) { this.state = state; } }
2.接著創建一個原發器對象Originator,定義了創建備忘錄對象和回滾的對象
public class Originator { private String state; public String getState() { return this.state; } public void setState(String state) { this.state = state; } /** * 創建對象 * * @return 備忘錄對象 */ public Memento createMemento() { return new Memento(state); } /** * 從備忘錄中恢復 * * @param memento 恢復的對象 */ public void restoreMemento(Memento memento) { this.state = memento.getState(); } }
3.創建備忘錄管理者Caretaker,顧名思義就是來管理備忘錄對象的
public class Caretaker { /** * 備忘錄對象 */ private Memento memento; /** * 獲取備忘錄 * * @return 備忘錄對象 */ public Memento retrieveMemento() { return this.memento; } /** * 存儲備忘錄對象 */ public void saveMemento(Memento memento) { this.memento = memento; } }
4.創建測試類
public class Client { public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); originator.setState("狀態A"); System.out.println("當前狀態:" + originator.getState()); // 存儲內部狀態 caretaker.saveMemento(originator.createMemento()); System.out.println("存檔"); // 改變狀態 originator.setState("狀態B"); System.out.println("當前狀態:" + originator.getState()); // 改變狀態 originator.setState("狀態C"); System.out.println("當前狀態:" + originator.getState()); // 恢復狀態 originator.restoreMemento(caretaker.retrieveMemento()); System.out.println("讀檔"); System.out.println("恢復后狀態:" + originator.getState()); } }
5.運行結果
當前狀態:狀態A 存檔 當前狀態:狀態B 當前狀態:狀態C 讀檔 恢復后狀態:狀態A黑箱實現
角色組成
MementoIF(備忘錄角色): 空接口,不作任何實現。
Originator(發起人(原發器)角色): 記錄當前時刻的內部狀態,負責定義哪些屬于備份范圍的狀態,負責創建和恢復備忘錄數據。這里Memento做為原發器的私有內部類,來存儲備忘錄。備忘錄只能由原發器對象來訪問它內部的數據,原發器外部的對象不應該能訪問到備忘錄對象的內部數據。
Caretaker(備忘錄負責人(管理者)角色): 對備忘錄對象進行管理,但是不能對備忘錄對象的內容進行操作或檢查。
Memento 對象給 Originator 角色對象提供一個寬接口,而為其他對象提供一個窄接口,即為黑箱實現。
UML結構圖
1.備忘錄窄接口
public interface MementoIF { }
2.接著創建一個原發器對象Originator,其中定義了一個私有化的內部類Memento實現了MementoIF接口,只有當前對象能訪問
public class Originator { private String state; public String getState() { return state; } public void setState(String state) { this.state = state; } /** * 創建一個新的備忘錄對象 */ public MementoIF createMemento() { return new Memento(state); } /** * 發起人恢復到備忘錄對象記錄的狀態 */ public void restoreMemento(MementoIF memento) { this.setState(((Memento) memento).getState()); } private class Memento implements MementoIF { private String state; private Memento(String state) { this.state = state; } private String getState() { return state; } private void setState(String state) { this.state = state; } } }
3.創建備忘錄管理者Caretaker,管理備忘錄對象的
public class Caretaker { /** * 備忘錄對象 */ private MementoIF memento; /** * 獲取備忘錄對象 */ public MementoIF retrieveMemento() { return memento; } /** * 保存備忘錄對象 */ public void saveMemento(MementoIF memento) { this.memento = memento; } }
4.創建測試類
public class Client { public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); originator.setState("狀態A"); System.out.println("當前狀態:" + originator.getState()); // 改變狀態 originator.setState("狀態B"); System.out.println("當前狀態:" + originator.getState()); // 存儲內部狀態 caretaker.saveMemento(originator.createMemento()); System.out.println("存檔"); // 改變狀態 originator.setState("狀態C"); System.out.println("當前狀態:" + originator.getState()); // 恢復狀態 originator.restoreMemento(caretaker.retrieveMemento()); System.out.println("讀檔"); System.out.println("恢復后狀態:" + originator.getState()); } }
5.運行結果
當前狀態:狀態A 當前狀態:狀態B 存檔 當前狀態:狀態C 讀檔 恢復后狀態:狀態B
兩者的代碼結構是比較類似的,本質區別就是外部能不能訪問備忘錄的狀態,備忘錄角色具有安全等級;這里關于備忘錄角色 -> 白箱實現利用的寬接口,黑箱模式利用的窄接口;
多重檢查點前面所給出的白箱和黑箱的示意性實現都是只存儲一個狀態的簡單實現,也可以叫做只有一個檢查點。常見的系統往往需要存儲不止一個狀態,而是需要存儲多個狀態,或者叫做有多個檢查點。 這種情況只需要使用有序隊列方式可以很容易達到多重檢查點
備忘錄模式可以將發起人對象的狀態存儲到備忘錄對象里面,備忘錄模式可以將發起人對象恢復到備忘錄對象所存儲的某一個檢查點上。
自述歷史所謂自述歷史模式(History-On-Self Pattern)實際上就是備忘錄模式的一個變種。在備忘錄模式中,發起人(Originator)角色、負責人(Caretaker)角色和備忘錄 (Memento)角色都是獨立的角色。雖然在實現上備忘錄類可以成為發起人類的內部成員類,但是備忘錄類仍然保持作為一個角色的獨立意義。在自述歷史模式里面,發起人角色自己兼任負責人角色。
完整代碼在GIT項目中
關于使用備忘錄的潛在代價:
標準的備忘錄模式的實現機制是依靠緩存來實現的,因此,當需要備忘的數據量較大時,或者是存儲的備忘錄對象數據量不大但是數量很多的時候,或者是用戶很頻繁的創建備忘錄對象的時候,這些都會導致非常大的開銷。
因此在使用備忘錄模式的時候,一定要好好思考應用的環境,如果使用的代價太高,就不要選用備忘錄模式,可以采用其它的替代方案。
關于增量存儲:
如果需要頻繁的創建備忘錄對象,而且創建和應用備忘錄對象來恢復狀態的順序是可控的,那么可以讓備忘錄進行增量存儲,也就是備忘錄可以僅僅存儲原發器內部相對于上一次存儲狀態后的增量改變。
比如:在命令模式實現可撤銷命令的實現中,就可以使用備忘錄來保存每個命令對應的狀態,然后在撤銷命令的時候,使用備忘錄來恢復這些狀態。由于命令的歷史列表是按照命令操作的順序來存放的,也是按照這個歷史列表來進行取消和重做的,因此順序是可控的。那么這種情況,還可以讓備忘錄對象只存儲一個命令所產生的增量改變而不是它所影響的每一個對象的完整狀態。
優點
給用戶提供了一種可以恢復狀態的機制,可以使用戶能夠比較方便地回到某個歷史的狀態。
實現了信息的封裝,使得用戶不需要關心狀態的保存細節。
缺點
消耗資源。如果類的成員變量過多,勢必會占用比較大的資源,而且每一次保存都會消耗一定的內存。
由于備份的信息是由發起人自己提供的,所以管理者無法預知備份的信息的大小,所以在客戶端使用時,可能一個操作占用了很大的內存,但客戶端并不知曉。
適用場景
需要保存/恢復數據的相關狀態場景。
提供一個可回滾的操作。
備忘錄模式在很多軟件的使用過程中普遍存在,但是在應用軟件開發中,它的使用頻率并不太高;
說點什么參考文獻:http://www.cnblogs.com/JsonShare/p/7283972.html
全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter16/battcn-memento
個人QQ:1837307557
battcn開源群(適合新手):391619659
微信公眾號:battcn(歡迎調戲)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70734.html
摘要:用專業的話來講設計模式是一套被反復使用多數人知曉的經過分類編目的代碼設計經驗的總結創建型模式,共五種工廠方法模式抽象工廠模式單例模式建造者模式原型模式。工廠方法模式的擴展性非常優秀。工廠方法模式是典型的解耦框架。 前言 最近一直在Java方向奮斗《終于,我還是下決心學Java后臺了》,今天抽空開始學習Java的設計模式了。計劃有時間就去學習,你這么有時間,還不來一起上車嗎? 之所以要學...
摘要:扎實基礎幸好自己之前花了大力氣去給自己打基礎,讓自己現在的基礎還算不錯。 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于 Vue版本 【2.5.17】 如果你覺得排版難看,請點擊 下面鏈接 或者 拉到 下面關注公眾號也可以吧 【Vue原理】Vue源碼閱讀總結大會 - 序 閱讀源碼是需...
摘要:如果你的運行緩慢,你可以考慮是否能優化請求,減少對的操作,盡量少的操,或者犧牲其它的來換取性能。在認識描述這些核心元素的過程中,我們也會分享一些當我們構建的時候遵守的一些經驗規則,一個應用應該保持健壯和高性能來維持競爭力。 一個開源的前端錯誤收集工具 frontend-tracker,你值得收藏~ 蒲公英團隊最近開發了一款前端錯誤收集工具,名叫 frontend-tracker ,這款...
摘要:本文只是尋找設計模式在中的應用。來補全這一塊工廠模式中的應用包線程池解釋和代碼線程池中有線程創建工廠。狀態模式中的應用解釋和代碼根據一個指針的狀態而改變自己的行為適配器模式中的應用解釋和代碼將一個類的接口轉換成客戶希望的另外一個接口。 前言 最近重學設計模式,而且還有很多源碼要看。所以就想一舉兩得。從源碼中尋找設計模式。順便還可以看看源碼。。。本文只是尋找設計模式在java中的應用。優...
閱讀 1054·2019-08-30 12:57
閱讀 2132·2019-08-30 11:11
閱讀 2183·2019-08-29 15:20
閱讀 1876·2019-08-29 14:12
閱讀 3279·2019-08-28 17:51
閱讀 2382·2019-08-26 13:23
閱讀 800·2019-08-26 10:34
閱讀 3861·2019-08-23 12:37