设计模式解密(17)- 备忘录模式

 

1、简介

定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态。

解释:也就是说,不破坏源数据的情况下,将源数据进行一次或者多次的备份。

本质:保存和恢复内部状态。

英文:Memento

类型:行为型

2、类图及组成(白箱实现与黑箱实现)

前言:备忘录模式按照备忘录角色的形态不同,分为白箱实现与黑箱实现,两种模式与备忘录角色提供的接口模式有关;

由于备忘录角色构成不同,所以这里拆分成两种类图分别解释,对比一下,就能明白:

白箱实现:

(引)类图:

组成:

  备忘录角色 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());
    }  
}

黑箱实现:

(引)类图:

组成:

  备忘录角色 MementoIF:空接口,不作任何实现。

  发起人(原发器)角色 Originator:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。PS:这里Memento做为原发器的私有内部类,来存储备忘录。备忘录只能由原发器对象来访问它内部的数据,原发器外部的对象不应该能访问到备忘录对象的内部数据。

  备忘录负责人(管理者)角色 Caretaker:对备忘录对象进行管理,但是不能对备忘录对象的内容进行操作或检查。

代码结构:

说明:
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、备忘录模式的变形:“自述历史”模式

(引)类图:

组成:

  备忘录角色:

    (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());
    }

}

结果:

移动位置→ 棋子:马||位置:位置1
移动位置→ 棋子:马||位置:位置2
我要悔棋(棋子回到上一个状态)→ 棋子:马||位置:位置1

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());
    }
}

结果:

原始位置→ 棋子:马||位置:原始位置
移动位置→ 棋子:马||位置:位置1
移动位置→ 棋子:马||位置:位置2
移动位置→ 棋子:马||位置:位置3
移动位置→ 棋子:马||位置:位置4
--------------------------------------
毁一步棋→ 棋子:马||位置:位置3
再毁一步棋→ 棋子:马||位置:位置2

8、优缺点

优点:

  1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。

  2、实现了信息的封装,使得用户不需要关心状态的保存细节。

缺点:

  1、消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

  2、由于备份的信息是由发起人自己提供的,所以管理者无法预知备份的信息的大小,所以在客户端使用时,可能一个操作占用了很大的内存,但客户端并不知晓。

9、应用场景

  1、需要保存/恢复数据的相关状态场景。

  2、提供一个可回滚的操作。

PS:备忘录模式在很多软件的使用过程中普遍存在,但是在应用软件开发中,它的使用频率并不太高;

10、总结

关于使用备忘录的潜在代价:

  标准的备忘录模式的实现机制是依靠缓存来实现的,因此,当需要备忘的数据量较大时,或者是存储的备忘录对象数据量不大但是数量很多的时候,或者是用户很频繁的创建备忘录对象的时候,这些都会导致非常大的开销。

  因此在使用备忘录模式的时候,一定要好好思考应用的环境,如果使用的代价太高,就不要选用备忘录模式,可以采用其它的替代方案。

关于增量存储:

  如果需要频繁的创建备忘录对象,而且创建和应用备忘录对象来恢复状态的顺序是可控的,那么可以让备忘录进行增量存储,也就是备忘录可以仅仅存储原发器内部相对于上一次存储状态后的增量改变。

  比如:在命令模式实现可撤销命令的实现中,就可以使用备忘录来保存每个命令对应的状态,然后在撤销命令的时候,使用备忘录来恢复这些状态。由于命令的历史列表是按照命令操作的顺序来存放的,也是按照这个历史列表来进行取消和重做的,因此顺序是可控的。那么这种情况,还可以让备忘录对象只存储一个命令所产生的增量改变而不是它所影响的每一个对象的完整状态。

 

PS:源码地址   https://github.com/JsonShare/DesignPattern/tree/master 

   

PS:原文地址  http://www.cnblogs.com/JsonShare/p/7283972.html

    

posted @ 2017-08-04 12:15  Json_wangqiang  阅读(1195)  评论(0编辑  收藏  举报