目錄
本文的結構如下:
- 引言
- 什么是備忘錄模式
- 模式的結構
- 典型代碼
- 代碼示例
- 優(yōu)點和缺點
- 適用環(huán)境
- 模式應用
一、引言
晚上躺在被窩里括改,突兀的腻豌,腦海主動把往事翻出來倒帶重播,那些舊時光的畫面很清晰,就像投影片一樣投在雪白的墻面饲梭,回憶著乘盖,便思緒萬千,下意識發(fā)出一聲輕嘆憔涉,哎订框,看樣子要失眠了。
有很多遺憾兜叨,有很多舍不得穿扳,只是怎么也回不去啊,人生又不是一部重生小說国旷,夏洛也只出現在熒幕中矛物。
所以說,好好敲代碼吧跪但,代碼敲錯了履羞,可以ctrl+z,代碼提交錯了屡久,可以git reset忆首。
在軟件開發(fā)中,很多時候需要記錄一個對象的內部狀態(tài)被环,目的就是為了允許用戶取消不確定或者錯誤的操作糙及,能夠恢復到原先的狀態(tài),使得有“后悔藥”可吃筛欢。備忘錄模式是一種給軟件提供后悔藥的機制浸锨,通過它可以使系統恢復到某一特定的歷史狀態(tài)。
二版姑、什么是備忘錄模式
備忘錄模式提供了一種狀態(tài)恢復的實現機制柱搜,使得用戶可以方便地回到一個特定的歷史步驟。
備忘錄模式定義如下:
備忘錄模式(Memento Pattern):在不破壞封裝的前提下漠酿,捕獲一個對象的內部狀態(tài)冯凹,并在該對象之外保存這個狀態(tài)谎亩,這樣可以在以后將對象恢復到原先保存的狀態(tài)炒嘲。它是一種對象行為型模式,其別名為Token匈庭。
三夫凸、模式的結構
備忘錄模式UML類圖如下:
備忘錄模式主要包含入下幾個角色:
- Originator(原發(fā)器):它是一個普通類,可以創(chuàng)建一個備忘錄阱持,并儲存該類當前的一些內部狀態(tài)夭拌,也可以使用備忘錄來恢復其內部狀態(tài),一般將需要保存內部狀態(tài)的類設計為原發(fā)器。
- Memento(備忘錄):存儲原發(fā)器的內部狀態(tài)鸽扁,根據原發(fā)器來決定保存哪些內部狀態(tài)蒜绽。備忘錄的設計一般可以參考原發(fā)器的設計,根據實際需要確定備忘錄類中的屬性桶现。需要注意的是躲雅,除了原發(fā)器本身與負責人類之外,備忘錄對象不能直接供其他類使用骡和。
- Caretaker(負責人):負責人又稱為管理者相赁,它負責保存?zhèn)渫洠遣荒軐渫浀膬热葸M行操作或檢查慰于。在負責人類中可以存儲一個或多個備忘錄對象钮科,它只負責存儲對象,而不能修改對象婆赠,也無須知道對象的實現細節(jié)绵脯。
在備忘錄模式中,最重要的就是備忘錄Memento了休里。由于在備忘錄中存儲的是原發(fā)器的中間狀態(tài)桨嫁,因此需要防止原發(fā)器以外的其他對象訪問備忘錄,特別是不允許其他對象來修改備忘錄份帐。
為了不破壞備忘錄的封裝性璃吧,我們需要對備忘錄的訪問做些控制:
- 對原發(fā)器:可以訪問備忘錄里的所有信息。
- 對負責人:不可以訪問備忘錄里面的數據废境,但是他可以保存?zhèn)渫洸⑶铱梢詫渫泜鬟f給其他對象畜挨。
- 其他對象:不可訪問也不可以保存,它只負責接收從負責人那里傳遞過來的備忘錄同時恢復原發(fā)器的狀態(tài)噩凹。
所以就備忘錄模式而言理想的情況就是只允許生成該備忘錄的那個原發(fā)器訪問備忘錄的內部狀態(tài)巴元。
四、典型代碼
在真實業(yè)務中驮宴,原發(fā)器類是一個具體的業(yè)務類逮刨,它包含一些用于存儲成員數據的屬性,原發(fā)器典型代碼如下:
public class Originator {
private String state;
public void restoreMemento(Memento m){
this.state = m.getState();
}
public Memento createMemento(){
return new Memento(state);
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String toString(){
return "originator---" + state;
}
}
對于備忘錄類Memento而言堵泽,它通常提供了與原發(fā)器相對應的屬性(可以是全部修己,也可以是部分)用于存儲原發(fā)器的狀態(tài)。
在設計備忘錄類時需要考慮其封裝性迎罗,除了Originator類睬愤,不允許其他類來調用備忘錄類Memento的構造函數與相關方法,如果不考慮封裝性纹安,允許其他類調用setState()等方法尤辱,將導致在備忘錄中保存的歷史狀態(tài)發(fā)生改變砂豌,通過撤銷操作所恢復的狀態(tài)就不再是真實的歷史狀態(tài),備忘錄模式也就失去了本身的意義光督。
在使用Java語言實現備忘錄模式時阳距,一般通過將Memento類與Originator類定義在同一個包(package)中來實現封裝,在Java語言中可使用默認訪問標識符來定義Memento類结借,即保證其包內可見娄涩。只有Originator類可以對Memento進行訪問,而限制了其他類對Memento的訪問映跟。在Memento中保存了Originator的state值蓄拣,如果Originator中的state值改變之后需撤銷,可以通過調用它的restoreMemento()方法進行恢復努隙。
典型代碼如下:
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
負責人類Caretaker用于保存?zhèn)渫泴ο笄蛐簦⑻峁ゞetMemento()方法用于向客戶端返回一個備忘錄對象,原發(fā)器通過使用這個備忘錄對象可以回到某個歷史狀態(tài)荸镊。負責人典型代碼如下:
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
簡單測試一下:
public class MementoDemo {
public static void main(String[] args) {
Originator originator = new Originator();
originator.setState("state1");
System.out.println(originator);
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
originator.setState("state2");
System.out.println(originator);
originator.restoreMemento(caretaker.getMemento());
System.out.println(originator);
}
}
五咽斧、代碼示例
剛上大學那會迷上籃球,玩游戲也都和籃球相關躬存,大一暑假张惹,便安裝了2K 11,自建了一個大中鋒岭洲,然后修改器身高調到最高宛逗,力量調到最大,速度調到最快盾剩,投籃調到最好雷激,為得當然是刷數據了(捂臉)。記得有一場比賽告私,辛苦打了40分鐘屎暇,數據超好,比分落后2分驻粟,還剩最后1s根悼,有三分絕殺的機會,這個情況下當然是先存檔蜀撑,絕殺不中可以回檔挤巡,總會投中的。
這里就以此為例屯掖。
5.1玄柏、第一版
public class Game {
private int ourScore;//我方分數
private int oppositeScore;//對方分數
private boolean end;
public Game(int ourScore, int oppositeScore) {
this.ourScore = ourScore;
this.oppositeScore = oppositeScore;
}
/**
* 我方投籃
* @param goal
* @param point
*/
public void shoot(boolean goal, int point){
if (end){
System.out.println("比賽已經結束,接收現實少年");
}else {
if (goal){
this.ourScore += point;
}
}
}
/**
* 對方投籃
* @param goal
* @param point
*/
public void autoShoot(boolean goal, int point){
if (end){
System.out.println("比賽已經結束贴铜,接收現實少年");
}else {
if (goal){
this.oppositeScore += point;
}
}
}
/**
* 回檔
* @param record
*/
public void restoreRecord(Record record){
this.end = false;
this.ourScore = record.getOurScore();
this.oppositeScore = record.getOppositeScore();
}
/**
* 存檔
* @return
*/
public Record saveRecord(){
return new Record(ourScore, oppositeScore);
}
public void showGame(){
System.out.println("我方得分:" + ourScore + ",對方得分:" + oppositeScore);
if (end){
System.out.println((ourScore > oppositeScore ? ",我方獲勝" : ourScore == oppositeScore ? ",打平進入加時" : "绍坝,對方獲勝"));
}
}
public int getOurScore() {
return ourScore;
}
public int getOppositeScore() {
return oppositeScore;
}
public void setOurScore(int ourScore) {
this.ourScore = ourScore;
}
public void setOppositeScore(int oppositeScore) {
this.oppositeScore = oppositeScore;
}
public boolean isEnd() {
return end;
}
public void setEnd(boolean end) {
this.end = end;
}
}
存檔:
class Record {
private int ourScore;//我方分數
private int oppositeScore;//對方分數
public Record(int ourScore, int oppositeScore) {
this.ourScore = ourScore;
this.oppositeScore = oppositeScore;
}
public void setOurScore(int ourScore) {
this.ourScore = ourScore;
}
public void setOppositeScore(int oppositeScore) {
this.oppositeScore = oppositeScore;
}
public int getOurScore() {
return ourScore;
}
public int getOppositeScore() {
return oppositeScore;
}
}
GameCaketaker持有存檔:
public class GameCaretaker {
private Record record;
public Record getRecord() {
return record;
}
public void setRecord(Record record) {
this.record = record;
}
}
測試:
public class MementoDemo {
public static void main(String[] args) {
GameCaretaker caretaker = new GameCaretaker();
Game game = new Game(97, 99);
//先存檔
caretaker.setRecord(game.saveRecord());
shoot(game, false, 3);
//回檔
game.restoreRecord(caretaker.getRecord());
shoot(game,false, 3);
//再回檔
game.restoreRecord(caretaker.getRecord());
shoot(game, true, 3);
}
private static void shoot(Game game, boolean goal, int point ){
game.shoot(goal, point);
game.setEnd(true);
game.showGame();
}
}
已經可以用了徘意,但是會發(fā)現這里只能回退一步,只能回到上一個最新的存檔轩褐,下面看看多步回退椎咧。
5.2、第二版
public class GameCaretaker {
private Map<Integer, Record> records = new HashMap<Integer, Record>();
public Record getRecords(Integer key) {
if (records.containsKey(key)){
return records.get(key);
}else {
throw new RuntimeException();
}
}
public void setRecords(Integer key, Record record) {
records.put(key, record);
}
public void showRecords(){
System.out.println("有" + records.keySet().size() + "個存檔把介,如下:");
for (Integer key: records.keySet()){
System.out.println("存檔" + key);
}
}
}
測試:
public class MementoDemo {
private static GameCaretaker caretaker = new GameCaretaker();
private static Game game = new Game(98,100);
private static Integer key = 1;
public static void main(String[] args) {
play(false, 2, false, 3);
game.showGame();
showRecords();
System.out.println("================================================================");
play(true, 2, true, 2);
game.showGame();
showRecords();
System.out.println("================================================================");
play(true, 3, true, 1);
game.showGame();
showRecords();
System.out.println("================================================================");
play(false, 2, true, 3);
game.setEnd(true);
game.showGame();
System.out.println("================================================================");
//回到存檔1
System.out.println("回到存檔1");
game.restoreRecord(caretaker.getRecords(1));
play(false, 2, true, 3);
game.setEnd(true);
game.showGame();
}
private static void play(boolean goal1, int point1, boolean goal2, int point2){
game.autoShoot(goal1, point1);
game.shoot(goal2, point2);
caretaker.setRecords(key++, game.saveRecord());
}
private static void showRecords(){
caretaker.showRecords();
}
}
六勤讽、優(yōu)點和缺點
6.1、優(yōu)點
備忘錄模式的主要優(yōu)點如下:
- 它提供了一種狀態(tài)恢復的實現機制拗踢,使得用戶可以方便地回到一個特定的歷史步驟脚牍,當新的狀態(tài)無效或者存在問題時,可以使用暫時存儲起來的備忘錄將狀態(tài)復原巢墅。
- 備忘錄實現了對信息的封裝诸狭,一個備忘錄對象是一種原發(fā)器對象狀態(tài)的表示,不會被其他代碼所改動君纫。備忘錄保存了原發(fā)器的狀態(tài)驯遇,采用列表、堆棧等集合來存儲備忘錄對象可以實現多次撤銷操作蓄髓。
6.2叉庐、缺點
備忘錄模式的主要缺點如下:
- 資源消耗過大,如果需要保存的原發(fā)器類的成員變量太多会喝,就不可避免需要占用大量的存儲空間眨唬,每保存一次對象的狀態(tài)都需要消耗一定的系統資源。
七好乐、適用環(huán)境
備忘錄模式在很多軟件的使用過程中普遍存在匾竿,但是在應用軟件開發(fā)中,它的使用頻率并不太高蔚万,因為現在很多基于窗體和瀏覽器的應用軟件并沒有提供撤銷操作岭妖。
在以下情況下可以考慮使用備忘錄模式:
- 需要保存一個對象在某一個時刻的狀態(tài)或部分狀態(tài)。
- 防止外界對象破壞一個對象歷史狀態(tài)的封裝性反璃,避免將對象歷史狀態(tài)的實現細節(jié)暴露給外界對象昵慌。
八、模式應用
在一些字處理軟件淮蜈、圖像編輯軟件斋攀、數據庫管理系統等軟件中備忘錄模式都得到了很好的應用。