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

Posted 与其临渊羡鱼,不如退而结网

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式解密(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();
           } 以上是关于设计模式解密(17)- 备忘录模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式——17.备忘录模式

备忘录模式(17)

可以解密加密数据的片段吗?

用于从 cloudkit 检索单列的代码模式/片段

设计模式备忘录模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )

23种设计模式归纳总结——行为型