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