當前位置:
首頁 > 知識 > 設計模式解密(17)- 備忘錄模式

設計模式解密(17)- 備忘錄模式

設計模式解密(17)- 備忘錄模式

1、簡介

定義:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣就可以將該對象恢復到原先保存的狀態。

解釋:也就是說,不破壞源數據的情況下,將源數據進行一次或者多次的備份。

本質:保存和恢復內部狀態。

英文:Memento

類型:行為型

2、類圖及組成(白箱實現與黑箱實現)

前言:備忘錄模式按照備忘錄角色的形態不同,分為白箱實現與黑箱實現,兩種模式與備忘錄角色提供的介面模式有關;

由於備忘錄角色構成不同,所以這裡拆分成兩種類圖分別解釋,對比一下,就能明白:

白箱實現:

(引)類圖:

設計模式解密(17)- 備忘錄模式

組成:

備忘錄角色 Memento:負責存儲原發器對象的內部狀態,但是具體需要存儲哪些數據是由原發器對象來決定的,在需要的時候提供原發器需要的內部狀態。PS:這裡可以存儲狀態。

發起人(原發器)角色 Originator:記錄當前時刻的內部狀態,負責定義哪些屬於備份範圍的狀態,負責創建和恢復備忘錄數據。

備忘錄負責人(管理者)角色 Caretaker:對備忘錄對象進行管理,但是不能對備忘錄對象的內容進行操作或檢查。

代碼結構:

說明:
備忘錄角色對任何對象都提供一個(寬)介面,備忘錄角色的內部所存儲的狀態就對所有對象公開。即白箱實現。
白箱實現發起人和負責人提供相同介面,使得負責人可以訪問備忘錄全部內容,並不安全。
白箱實現對備忘錄內容的保護靠的是程序員的自律,實現也很簡單。

/**
* 備忘錄
*/
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;
}
}

/**
* 原發器對象
*/
public class Originator {
/**
* 需要存儲的狀態,也有不需要存儲的狀態
*/
private String state;

public String getState {
return this.state;
}

public void setState(String state) {
this.state = state;
}

//創建備忘錄對象
public Memento createMemento {
return new Memento(state);
}

//從備忘錄中恢復狀態
public void restoreMemento(Memento memento) {
this.state = memento.getState;
}
}

/**
* 備忘錄管理者
*/
public class Caretaker {
/**
* 備忘錄對象
*/
private Memento memento;

//獲取備忘錄
public Memento retrieveMemento {
return this.memento;
}

//存儲備忘錄對象
public void saveMemento(Memento memento) {
this.memento = memento;
}
}

/**
* 客戶端調用示例
*/
public class Client {
public static void main(String[] args) {
Originator originator = new Originator;
Caretaker caretaker = new Caretaker;

originator.setState("狀態1");
System.out.println("當前狀態:"+originator.getState);
// 存儲內部狀態
caretaker.saveMemento(originator.createMemento);

// 改變狀態
originator.setState("狀態2");
System.out.println("當前狀態:"+originator.getState);
// 恢復狀態
originator.restoreMemento(caretaker.retrieveMemento);

System.out.println("恢復後狀態:"+originator.getState);
}
}

黑箱實現:

(引)類圖:

設計模式解密(17)- 備忘錄模式

組成:

備忘錄角色 MementoIF:空介面,不作任何實現。

發起人(原發器)角色 Originator:記錄當前時刻的內部狀態,負責定義哪些屬於備份範圍的狀態,負責創建和恢復備忘錄數據。PS:這裡Memento做為原發器的私有內部類,來存儲備忘錄。備忘錄只能由原發器對象來訪問它內部的數據,原發器外部的對象不應該能訪問到備忘錄對象的內部數據。

說明:
Memento 對象給 Originator 角色對象提供一個寬介面,而為其他對象提供一個窄介面,即黑箱實現。

/**
* 備忘錄窄介面
*/
public interface 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;
}
}
}

/**
* 備忘錄管理者
*/
public class Caretaker {
/**
* 備忘錄對象
*/
private MementoIF memento;

/**
* 獲取備忘錄對象
*/
public MementoIF retrieveMemento {
return memento;
}

/**
* 保存備忘錄對象
*/
public void saveMemento(MementoIF memento) {
this.memento = memento;
}
}

/**
* 客戶端調用示例
*/
public class Client {
public static void main(String[] args) {
Originator originator = new Originator;
Caretaker caretaker = new Caretaker;

originator.setState("狀態1");
System.out.println("當前狀態:"+originator.getState);
// 存儲內部狀態
caretaker.saveMemento(originator.createMemento);

// 改變狀態
originator.setState("狀態2");
System.out.println("當前狀態:"+originator.getState);
// 恢復狀態
originator.restoreMemento(caretaker.retrieveMemento);

System.out.println("恢復後狀態:"+originator.getState);
}
}

引入兩個定義,備忘錄有兩個等效的介面:

● 窄介面:負責人(Caretaker)對象(和其他除發起人對象之外的任何對象)看到的是備忘錄的窄介面(narrow interface),這個窄介面只允許它把備忘錄對象傳給其他的對象。

● 寬介面:與負責人對象看到的窄介面相反的是,發起人對象可以看到一個寬介面(wide interface),這個寬介面允許它讀取所有的數據,以便根據這些數據恢復這個發起人對象的內部狀態。

備忘錄角色對任何對象都提供一個(寬)介面,備忘錄角色的內部所存儲的狀態就對所有對象公開。即白箱實現。

白箱實現發起人和負責人提供相同介面,使得負責人可以訪問備忘錄全部內容,並不安全。

白箱實現對備忘錄內容的保護靠的是程序員的自律,實現也很簡單。

Memento 對象給 Originator 角色對象提供一個寬介面,而為其他對象提供一個窄介面,即黑箱實現。

大家看完,兩者的代碼結構,是不是對白箱實現與黑箱實現有了一定了解;其實很簡單,本質區別就是外部能不能訪問備忘錄的狀態,備忘錄角色具有安全等級;這裡關於備忘錄角色 -> 白箱實現利用的寬介面,黑箱模式利用的窄介面;

3、實例引入

這裡用白箱實現,來引入實例;

場景:大家都應該玩過象棋,沒玩過也見過吧;象棋里存在悔棋的行為,下面就利用備忘錄模式模擬悔棋操作;(這裡簡化邏輯,旨在明白怎樣使用)

package com.designpattern.Memento.SingleCheckpoints;

/**
* 象棋備忘錄角色
* @author Json<<json1990@foxmail.com>>
*/
public class ChessMemento {
//操作的棋子
private String piece;
//所在位置
private String location;

public ChessMemento {

}

public ChessMemento(String piece,String location) {
this.piece = piece;
this.location = location;
}

public String getPiece {
return piece;
}

public void setPiece(String piece) {
this.piece = piece;
}

public String getLocation {
return location;
}

public void setLocation(String location) {
this.location = location;
}
}

package com.designpattern.Memento.SingleCheckpoints;

/**
* 象棋原發器對象
* @author Json<<json1990@foxmail.com>>
*/
public class ChessOriginator {
//操作的棋子
private String piece;
//所在位置
private String location;

public ChessOriginator {

}

public ChessOriginator(String piece,String location) {
this.piece = piece;
this.location = location;
}

public String getPiece {
return piece;
}

public void setPiece(String piece) {
this.piece = piece;
}

public String getLocation {
return location;
}

public void setLocation(String location) {
this.location = location;
}

//保存狀態
public ChessMemento createMemento {
return new ChessMemento(this.piece,this.location);
}

//恢復狀態
public void restoreMemento(ChessMemento memento) {
this.piece = memento.getPiece;
this.location = memento.getLocation;
}
}

package com.designpattern.Memento.SingleCheckpoints;

/**
* 象棋備忘錄管理者角色
* @author Json<<json1990@foxmail.com>>
*/
public class ChessCaretaker {
/**
* 備忘錄對象
*/
private ChessMemento memento;

public ChessMemento retrieveMemento {
return this.memento;
}

public void saveMemento(ChessMemento memento) {
this.memento = memento;
}
}

下面測試:

package com.designpattern.Memento.SingleCheckpoints;

/**
* 測試
* @author Json<<json1990@foxmail.com>>
*/
public class Client {
public static void main(String[] args) {
ChessOriginator originator = new ChessOriginator;
ChessCaretaker caretaker = new ChessCaretaker;

originator.setPiece("馬");
originator.setLocation("位置1");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);
// 存儲內部狀態
caretaker.saveMemento(originator.createMemento);

// 改變狀態
originator.setPiece("馬");
originator.setLocation("位置2");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

// 恢復狀態
originator.restoreMemento(caretaker.retrieveMemento);
System.out.println("我要悔棋(棋子回到上一個狀態)→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);
}

}

結果:

移動位置→ 棋子:馬||位置:位置1
移動位置→ 棋子:馬||位置:位置2
我要悔棋(棋子回到上一個狀態)→ 棋子:馬||位置:位置1

4、備忘錄模式的變形:「自述歷史」模式

(引)類圖:

設計模式解密(17)- 備忘錄模式

組成:

備忘錄角色:

(1)將發起人(Originator)對象的內部狀態存儲起來。

(2)備忘錄可以保護其內容不被發起人(Originator)對象之外的任何對象所讀取。

發起人角色:

(1)創建一個含有它當前的內部狀態的備忘錄對象。

(2)使用備忘錄對象存儲其內部狀態。

客戶端角色:

負責保存備忘錄對象。

「自述歷史」模式(History-On-Self Pattern)實際上就是備忘錄模式的一個變種。在備忘錄模式中,發起人(Originator)角色、負責人(Caretaker)角色和備忘錄 (Memento)角色都是獨立的角色。雖然在實現上備忘錄類可以成為發起人類的內部成員類,但是備忘錄類仍然保持作為一個角色的獨立意義。在「自述歷 史」模式裡面,發起人角色自己兼任負責人角色。

代碼結構:

**自述歷史作為一個備忘錄模式的特殊實現形式非常簡單易懂**

/**
* 備忘錄窄介面
*/
public interface MementoIF {

}

/**
*發起人角色自己兼任負責人角色
*/
public class Originator {
private String state;

public String getState{
return state;
}

public void setState(String state){
this.state = state;
}

public Memento createMemento{
return new Memento(this);
}

public void restoreMemento(MementoIF memento){
Memento m = (Memento)memento;
setState(m.state);
}

private class Memento implements MementoIF{
private String state;

private Memento(Originator o){
this.state = o.state;
}

private String getState {
return state;
}

}
}

//調用示例
public class Client {
public static void main(String[] args) {
Originator o = new Originator;
// 修改狀態
o.setState("狀態1");
System.out.println("當前狀態:"+o.getState);

// 創建備忘錄
MementoIF memento = o.createMemento;
// 修改狀態
o.setState("狀態2");
System.out.println("當前狀態:"+o.getState);

// 按照備忘錄恢復對象的狀態
o.restoreMemento(memento);
System.out.println("當前狀態:"+o.getState);
}
}

5、單次備份與多次備份(單個檢查點與多個檢查點)

區別:只存儲一個狀態的簡單實現,也可以叫做只有一個檢查點。常見的系統往往需要存儲不止一個狀態,而是需要存儲多個狀態,或者叫做有多個檢查點。

前面所給出的白箱和黑箱的示意性實現都是只存儲一個狀態的簡單實現,也可以叫做只有一個檢查點。

PS:(黑箱實現、白箱實現、自述模式) 與 (單個檢查點與多個檢查點) 不衝突,他們是兩個維度;這點細細品味一下就不迷糊了!

在常見的系統往往需要存儲不止一個狀態,而是需要存儲多個狀態。

下面給出一個示意性的、有多重檢查點的備忘錄模式的實現(改造上面象棋實例):

package com.designpattern.Memento.MutilCheckpoints;

/**
* 象棋備忘錄角色
* @author Json<<json1990@foxmail.com>>
*/
public class ChessMemento {
//操作的棋子
private String piece;
//所在位置
private String location;

public ChessMemento {

}

public ChessMemento(String piece,String location) {
this.piece = piece;
this.location = location;
}

public String getPiece {
return piece;
}

public void setPiece(String piece) {
this.piece = piece;
}

public String getLocation {
return location;
}

public void setLocation(String location) {
this.location = location;
}
}

package com.designpattern.Memento.MutilCheckpoints;

/**
* 象棋原發器對象
* @author Json<<json1990@foxmail.com>>
*/
public class ChessOriginator {
//操作的棋子
private String piece;
//所在位置
private String location;

public ChessOriginator {

}

public ChessOriginator(String piece,String location) {
this.piece = piece;
this.location = location;
}

public String getPiece {
return piece;
}

public void setPiece(String piece) {
this.piece = piece;
}

public String getLocation {
return location;
}

public void setLocation(String location) {
this.location = location;
}

//保存狀態
public ChessMemento createMemento {
return new ChessMemento(this.piece,this.location);
}

//恢復狀態
public void restoreMemento(ChessMemento memento) {
this.piece = memento.getPiece;
this.location = memento.getLocation;
}
}

關於 備忘錄角色、原發器角色沒做修改,主要在負責人角色修改:

package com.designpattern.Memento.MutilCheckpoints;

import java.util.Stack;

/**
* 象棋備忘錄管理者角色
* @author Json<<json1990@foxmail.com>>
*/
public class ChessCaretaker {
private Stack<ChessMemento> stack;

public ChessCaretaker {
stack = new Stack<ChessMemento>;
}

/**
* 存儲備忘錄對象
*/
public void saveMemento(ChessMemento m) {
stack.push(m);
}

/**
* 獲取備忘錄對象
* @return
*/
public ChessMemento retrieveMemento {
if(stack.isEmpty){
System.out.println("不能再悔棋了!");
return null;
}
return stack.pop; //移除元素
}
}

測試:

package com.designpattern.Memento.MutilCheckpoints;

/**
* 測試
* @author Json<<json1990@foxmail.com>>
*/
public class Client {
public static void main(String[] args) {
ChessOriginator originator = new ChessOriginator;
ChessCaretaker caretaker = new ChessCaretaker;

originator.setPiece("馬");
originator.setLocation("原始位置");
System.out.println("原始位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

// 存儲內部狀態
caretaker.saveMemento(originator.createMemento);
originator.setPiece("馬");
originator.setLocation("位置1");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

// 存儲內部狀態
caretaker.saveMemento(originator.createMemento);
// 改變狀態
originator.setPiece("馬");
originator.setLocation("位置2");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

// 存儲內部狀態
caretaker.saveMemento(originator.createMemento);
// 改變狀態
originator.setPiece("馬");
originator.setLocation("位置3");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

// 存儲內部狀態
caretaker.saveMemento(originator.createMemento);
// 改變狀態
originator.setPiece("馬");
originator.setLocation("位置4");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

System.out.println("--------------------------------------");

// 恢復狀態
originator.restoreMemento(caretaker.retrieveMemento);
System.out.println("毀一步棋→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

//大哥我再毀一步棋
originator.restoreMemento(caretaker.retrieveMemento);
System.out.println("再毀一步棋→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);
}
}

結果:

原始位置→ 棋子:馬||位置:原始位置
移動位置→ 棋子:馬||位置:位置1
移動位置→ 棋子:馬||位置:位置2
移動位置→ 棋子:馬||位置:位置3
移動位置→ 棋子:馬||位置:位置4
--------------------------------------
毀一步棋→ 棋子:馬||位置:位置3
再毀一步棋→ 棋子:馬||位置:位置2

6、關於備忘錄的離線存儲

標準的備忘錄模式,沒有討論離線存儲的實現。

事實上,從備忘錄模式的功能和實現上,是可以把備忘錄的數據實現成為離線存儲的,也就是不僅限於存儲於內存中,可以把這些備忘數據存儲到文件中、xml中、資料庫中,從而支持跨越會話的備份和恢復功能。

離線存儲甚至能幫助應對應用崩潰,然後關閉重啟的情況,應用重啟過後,從離線存儲裡面獲取相應的數據,然後重新設置狀態,恢復到崩潰前的狀態。

當然,並不是所有的備忘數據都需要離線存儲,一般來講,需要存儲很長時間、或者需要支持跨越會話的備份和恢復功能、或者是希望系統關閉後還能被保存的備忘數據,這些情況建議採用離線存儲。

離線存儲的實現也很簡單,如果要實現離線存儲,主要需要修改管理者對象,把它保存備忘錄對象的方法,實現成為保存到文件中,而恢復備忘錄對象實現成為讀取文件就可以了。

下面再次改造象棋實例:

package com.designpattern.Memento.OfflineStorage;

import java.io.Serializable;

/**
* 象棋備忘錄角色 -- 實現序列化
* @author Json<<json1990@foxmail.com>>
*/
public class ChessMemento implements Serializable{
private static final long serialVersionUID = 1L;

//操作的棋子
private String piece;
//所在位置
private String location;

public ChessMemento {

}

public ChessMemento(String piece,String location) {
this.piece = piece;
this.location = location;
}

public String getPiece {
return piece;
}

public void setPiece(String piece) {
this.piece = piece;
}

public String getLocation {
return location;
}

public void setLocation(String location) {
this.location = location;
}
}

package com.designpattern.Memento.OfflineStorage;

/**
* 象棋原發器對象
* @author Json<<json1990@foxmail.com>>
*/
public class ChessOriginator{

//操作的棋子
private String piece;
//所在位置
private String location;

public ChessOriginator {

}

public ChessOriginator(String piece,String location) {
this.piece = piece;
this.location = location;
}

public String getPiece {
return piece;
}

public void setPiece(String piece) {
this.piece = piece;
}

public String getLocation {
return location;
}

public void setLocation(String location) {
this.location = location;
}

//保存狀態
public ChessMemento createMemento {
return new ChessMemento(this.piece,this.location);
}

//恢復狀態
public void restoreMemento(ChessMemento memento) {
this.piece = memento.getPiece;
this.location = memento.getLocation;
}
}

package com.designpattern.Memento.OfflineStorage;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
* 象棋備忘錄管理者角色 -- 實現序列化存儲和讀取到文件
* @author Json<<json1990@foxmail.com>>
*/
public class ChessCaretaker{
/**
* 保存備忘錄對象
* @param memento 被保存的備忘錄對象
*/
public void saveMemento(ChessMemento memento){
//寫到文件中
ObjectOutputStream out = null;
try{
out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("E:\ChessMemento")));
out.writeObject(memento);
}catch(Exception err){
err.printStackTrace;
}finally{
try {
out.close;
} catch (IOException e) {
e.printStackTrace;
}
}
}
/**
* 獲取被保存的備忘錄對象
* @return 被保存的備忘錄對象
*/
public ChessMemento retrieveMemento{
ChessMemento memento = null;
//從文件中獲取備忘錄數據
ObjectInputStream in = null;
try{
in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("E:\ChessMemento")));
memento = (ChessMemento)in.readObject;
}catch(Exception err){
err.printStackTrace;
}finally{
try {
in.close;
} catch (IOException e) {
e.printStackTrace;
}
}
return memento;
}
}

測試:

package com.designpattern.Memento.OfflineStorage;

/**
* 測試
* @author Json<<json1990@foxmail.com>>
*/
public class Client {
public static void main(String[] args) {
ChessOriginator originator = new ChessOriginator;
ChessCaretaker caretaker = new ChessCaretaker;

originator.setPiece("馬");
originator.setLocation("位置1");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);
// 存儲內部狀態
caretaker.saveMemento(originator.createMemento);

// 改變狀態
originator.setPiece("馬");
originator.setLocation("位置2");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

// 恢復狀態
originator.restoreMemento(caretaker.retrieveMemento);
System.out.println("我要悔棋(棋子回到上一個狀態)→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);
}

}

7、備忘錄實現撤銷操作(續命令模式--撤銷)

在命令模式中,講到了可撤銷的操作,在那裡講到:

有兩種基本的思路來實現可撤銷的操作:

1、逆向操作實現:比如被撤銷的操作是添加功能,那撤消的實現就變成刪除功能;同理被撤銷的操作是刪除功能,那麼撤銷的實現就變成添加功能;

2、存儲恢復實現,意思就是把操作前的狀態記錄下來,然後要撤銷操作的時候就直接恢復回去就可以了,可使用備忘錄模式(Memento Pattern)來實現;

這裡就來實現第二種方式,就是存儲恢複式,這裡還用象棋的例子,繼續改造,對比學習會比較清楚:

package com.designpattern.Memento.undoByMemento;

/**
* 象棋備忘錄角色
* @author Json<<json1990@foxmail.com>>
*/
public class ChessMemento {
//操作的棋子
private String piece;
//所在位置
private String location;

public ChessMemento {

}

public ChessMemento(String piece,String location) {
this.piece = piece;
this.location = location;
}

public String getPiece {
return piece;
}

public void setPiece(String piece) {
this.piece = piece;
}

public String getLocation {
return location;
}

public void setLocation(String location) {
this.location = location;
}
}

package com.designpattern.Memento.undoByMemento;

/**
* 象棋原發器對象
* @author Json<<json1990@foxmail.com>>
*/
public class ChessOriginator {
//操作的棋子
private String piece;
//所在位置
private String location;

public ChessOriginator {

}

public ChessOriginator(String piece,String location) {
this.piece = piece;
this.location = location;
}

public String getPiece {
return piece;
}

public void setPiece(String piece) {
this.piece = piece;
}

public String getLocation {
return location;
}

public void setLocation(String location) {
this.location = location;
}

//保存狀態
public ChessMemento createMemento {
return new ChessMemento(this.piece,this.location);
}

//恢復狀態
public void restoreMemento(ChessMemento memento) {
this.piece = memento.getPiece;
this.location = memento.getLocation;
}
}

package com.designpattern.Memento.undoByMemento;

import java.util.Stack;

/**
* 象棋備忘錄管理者角色
* @author Json<<json1990@foxmail.com>>
*/
public class ChessCaretaker {
private Stack<ChessMemento> stack;

public ChessCaretaker {
stack = new Stack<ChessMemento>;
}

/**
* 存儲備忘錄對象
*/
public void saveMemento(ChessMemento m) {
stack.push(m);
}

/**
* 獲取備忘錄對象
* @return
*/
public ChessMemento retrieveMemento {
if(stack.isEmpty){
System.out.println("不能再悔棋了!");
return null;
}
return stack.pop; //移除元素
}
}

這裡增加命令介面和實現類:

package com.designpattern.Memento.undoByMemento;

/**
* 包含撤銷命令的介面
* @author Json<<json1990@foxmail.com>>
*/
public interface Command {
//下棋操作
void execute;
//撤銷方法
void undo;
}

package com.designpattern.Memento.undoByMemento;

/**
* 具體命令 -- 悔棋命令
* @author Json<<json1990@foxmail.com>>
*/
public class TakeBackCommand implements Command {
private ChessCaretaker caretaker;
private ChessOriginator originator;

public TakeBackCommand(ChessOriginator originator,ChessCaretaker caretaker) {
this.originator = originator;
this.caretaker = caretaker;
}

/**
* 下棋
*/
@Override
public void execute {
caretaker.saveMemento(originator.createMemento); //保存備忘錄
}

/**
* 悔棋
*/
@Override
public void undo {
originator.restoreMemento(caretaker.retrieveMemento);//撤銷到上一個備忘錄
}

}

再建一個請求者角色類 :

package com.designpattern.Memento.undoByMemento;

/**
* 請求者角色類 -- 下棋人Invoker
* @author Json<<json1990@foxmail.com>>
*/
public class People {
private Command command;

public People(Command command){
this.command = command;
}

public void executeCreateCommand{
command.execute;
}

public void undoCreateCommand{
command.undo;
}
}

下面測試:

package com.designpattern.Memento.undoByMemento;

/**
* 測試
* @author Json<<json1990@foxmail.com>>
*/
public class Client {
public static void main(String[] args) {
//創建接收者
ChessOriginator originator = new ChessOriginator;
ChessCaretaker caretaker = new ChessCaretaker;
//創建命令對象,設定它的接收者
Command command = new TakeBackCommand(originator,caretaker);
//創建請求者
People people = new People(command);

originator.setPiece("馬");
originator.setLocation("原始位置");
System.out.println("原始位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

people.executeCreateCommand;
originator.setPiece("馬");
originator.setLocation("位置1");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

people.executeCreateCommand;
// 改變狀態
originator.setPiece("馬");
originator.setLocation("位置2");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

people.executeCreateCommand;
// 改變狀態
originator.setPiece("馬");
originator.setLocation("位置3");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

people.executeCreateCommand;
// 改變狀態
originator.setPiece("馬");
originator.setLocation("位置4");
System.out.println("移動位置→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

System.out.println("--------------------------------------");

people.undoCreateCommand;
System.out.println("毀一步棋→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);

//大哥我再毀一步棋
people.undoCreateCommand;
System.out.println("再毀一步棋→ "+"棋子:"+originator.getPiece+"||位置:"+originator.getLocation);
}
}

8、優缺點

優點:

1、給用戶提供了一種可以恢復狀態的機制,可以使用戶能夠比較方便地回到某個歷史的狀態。

2、實現了信息的封裝,使得用戶不需要關心狀態的保存細節。

缺點:

1、消耗資源。如果類的成員變數過多,勢必會佔用比較大的資源,而且每一次保存都會消耗一定的內存。

2、由於備份的信息是由發起人自己提供的,所以管理者無法預知備份的信息的大小,所以在客戶端使用時,可能一個操作佔用了很大的內存,但客戶端並不知曉。

9、應用場景

1、需要保存/恢複數據的相關狀態場景。

2、提供一個可回滾的操作。

PS:備忘錄模式在很多軟體的使用過程中普遍存在,但是在應用軟體開發中,它的使用頻率並不太高;

10、總結

關於使用備忘錄的潛在代價:

標準的備忘錄模式的實現機制是依靠緩存來實現的,因此,當需要備忘的數據量較大時,或者是存儲的備忘錄對象數據量不大但是數量很多的時候,或者是用戶很頻繁的創建備忘錄對象的時候,這些都會導致非常大的開銷。

因此在使用備忘錄模式的時候,一定要好好思考應用的環境,如果使用的代價太高,就不要選用備忘錄模式,可以採用其它的替代方案。

關於增量存儲:

如果需要頻繁的創建備忘錄對象,而且創建和應用備忘錄對象來恢復狀態的順序是可控的,那麼可以讓備忘錄進行增量存儲,也就是備忘錄可以僅僅存儲原發器內部相對於上一次存儲狀態後的增量改變。

比如:在命令模式實現可撤銷命令的實現中,就可以使用備忘錄來保存每個命令對應的狀態,然後在撤銷命令的時候,使用備忘錄來恢復這些狀態。由於命令的歷史列表是按照命令操作的順序來存放的,也是按照這個歷史列表來進行取消和重做的,因此順序是可控的。那麼這種情況,還可以讓備忘錄對象只存儲一個命令所產生的增量改變而不是它所影響的每一個對象的完整狀態。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 達人科技 的精彩文章:

九大排序演算法整合
Android Binder機制詳解:手寫IPC通信
[Android FrameWork 6.0源碼學習] View的重繪過程之Layout

TAG:達人科技 |

您可能感興趣

24種設計模式及案例
設計模式概念
小米平板4 MIUI10開始公測:支持1:2分屏模式
冒險模式第1431關通關技巧
一加5/5T正式推送安卓8.1:手勢、遊戲模式
E3 2018:《煮糊了2》正式揭曉 加入在線合作模式 8月7日發售!
3.拍攝模式的布局
2018年聯合辦公運營模式及競爭格局分析
地表第一台X330 BGA 3615qe 13.3fhd夏普w13單屏模式正式完工
LOL:8.16新模式極限閃擊正式上線!
諾基亞2.1/3.1/5.1多款低端機發布:搭載安卓系統 恐複製小米模式
LOL測試服8月21日:安妮加強 新模式大量英雄調整
2018 小程序開啟盈利模式
《生化危機2 重製版》支持4K或60幀 精簡戰役模式
F1官方遊戲《F1 2018》發售日正式公布 生涯模式將進一步擴展
12種駕駛模式!試駕全新BMW 530Le
體態代償模式的評估與康復 WH 北京7.5-8 上海7.12-15
NBA 2K18經理模式玩法技巧
1321期-3:技術免疫檢測中的反應模式
工業4.0時代設備管理模式探討