• 设计模式之备忘录模式


    定义

    在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
    如游戏中的存档,各种编辑器中的后退、撤销功能。

    结构

    • Originator,发起人角色,一个普通的业务处理类,可以根据自身创建备忘录对象,根据备忘录数据恢复自身。
    • Memento,备忘录角色,负责存储发起人的内部数据,在需要时根据备忘录来恢复发起人。
    • Caretaker,备忘录管理者,提供保存和获取备忘录的功能。

    简单实现

    发起人

    /**
     * 发起者
     */
    public class Originator {
    
      private String state;
    
      public void setState(String state) {
        this.state = state;
      }
    
      public String getState() {
        return state;
      }
    
      public Memento createMemento() {
        return new Memento(state);
      }
    
      public void restoreMemento(Memento m) {
        this.setState(m.getState());
      }
    
    }
    

    备忘录实现

    /**
     * 备忘录
     */
    public class Memento {
    
      private String state;
    
      public Memento(String state) {
        this.state = state;
      }
    
      public void setState(String state) {
        this.state = state;
      }
    
      public String getState() {
        return state;
      }
    }
    

    备忘录管理者

    /**
     * 管理者
     */
    public class Caretaker {
    
      private Memento memento;
    
      public void setMemento(Memento m) {
        memento = m;
      }
    
      public Memento getMemento() {
        return memento;
      }
    }
    

    客户端

    public class Client {
    
      public static void main(String[] args) {
        Originator or = new Originator();
        Caretaker cr = new Caretaker();
        or.setState("S0");
        System.out.println("初始状态:" + or.getState());
        cr.setMemento(or.createMemento()); //保存状态
        or.setState("S1");
        System.out.println("新的状态:" + or.getState());
        or.restoreMemento(cr.getMemento()); //恢复状态
        System.out.println("恢复状态:" + or.getState());
      }
    
    }
    

    输出结果为

    初始状态:S0
    新的状态:S1
    恢复状态:S0
    

    使用窄接口实现不可修改的备忘录

    备忘录对象不应该被除了发起人对象之外的对象访问和修改,这里我们引入一个窄接口,不包含任何方法,只是作为标识接口。具体的备忘录实现只有发起人才能访问。

    /**
     * 备忘录
     */
    public interface Memento {
    
    }
    

    发起人,内部包含备忘录的具体实现

    /**
     * 发起者
     */
    public class Originator {
    
      private String state;
    
      public void setState(String state) {
        this.state = state;
      }
    
      public String getState() {
        return state;
      }
    
      public Memento createMemento() {
        return new MementoImpl(state);
      }
    
      public void restoreMemento(Memento m) {
        this.setState(((MementoImpl)m).getState());
      }
    
      private static class MementoImpl implements Memento {
    
        private String state;
    
        public MementoImpl(String state) {
          this.state = state;
        }
    
        public void setState(String state) {
          this.state = state;
        }
    
        public String getState() {
          return state;
        }
      }
    }
    

    备忘录管理者和客户端都不变。

    实现撤销和恢复功能

    之前学习到 命令模式 实现撤销功能时,有两种思路来实现

    • 一种是补偿式,又称反操作式,比如被撤销的操作是添加,撤销就是删除。
    • 另一种是存储恢复式,将操作前的状态记录下来,撤销的时候直接恢复回去就可以了。

    这里就使用第二种方式再实现一次。

    命令接收者

    /**
     * 命令接收者
     */
    public class Receiver {
    
      /**
       * 文本内容
       */
      private String textContent = "";
    
      /**
       * 文本追加
       */
      public void append(String target) {
        System.out.println("操作前内容:" + textContent);
        textContent = textContent.concat(target);
        System.out.println("操作后内容:" + textContent);
      }
    
      /**
       * 文本删除
       */
      public void remove(String target) {
        System.out.println("操作前内容:" + textContent);
        if (textContent.endsWith(target)) {
          textContent = textContent.substring(0, textContent.length() - target.length());
        }
        System.out.println("操作后内容:" + textContent);
      }
    
      public Memento createMemento() {
        return new MementoImpl(textContent);
      }
    
      public void restoreMemento(Memento m) {
        System.out.println("操作前内容:" + textContent);
        textContent = ((MementoImpl) m).getTextContent();
        System.out.println("操作后内容:" + textContent);
      }
    
      public static class MementoImpl implements Memento {
    
        private String textContent;
    
        public MementoImpl(String textContent) {
          this.textContent = textContent;
        }
    
        public String getTextContent() {
          return textContent;
        }
      }
    }
    

    命令接收者就是备忘录模式中的发起人角色。

    备忘录接口

    /**
     * 备忘录
     */
    public interface Memento {
    
    }
    

    命令接口

    public interface Command {
    
      /**
       * 命令执行
       */
      void execute();
    
      /**
       * 命令撤销
       */
      void undo(Memento m);
    
      /**
       * 创建备忘录
       */
      Memento createMemento();
    }
    

    根据备忘录对象来撤销。

    抽象命令类

    public abstract class AbstractCommand implements Command {
    
    
      protected Receiver receiver;
      protected String target;
    
      protected AbstractCommand(Receiver receiver, String target) {
        this.receiver = receiver;
        this.target = target;
      }
    
      @Override
      public void undo(Memento m) {
        receiver.restoreMemento(m);
      }
    
      @Override
      public Memento createMemento() {
        return receiver.createMemento();
      }
    }
    

    对于所有的命令实现,它们的撤销都是一样的,都是根据备忘录对象来撤销,所以这里实现一个公共的命令对象。

    具体的命令实现

    /**
     * 文本追加命令
     */
    public class AppendCommand extends AbstractCommand {
    
      public AppendCommand(Receiver receiver, String target) {
        super(receiver, target);
      }
    
      @Override
      public void execute() {
        receiver.append(target);
      }
    
    }
    
    /**
     * 文本删除命令
     */
    public class RemoveCommand extends AbstractCommand {
    
      public RemoveCommand(Receiver receiver, String target) {
        super(receiver, target);
      }
    
      @Override
      public void execute() {
        receiver.remove(target);
      }
    
    }
    

    文本编辑器

    import java.util.Stack;
    
    /**
     * 文本编辑器,支持撤销和恢复
     */
    public class TextEditor {
    
      private Command command;
      //操作的历史记录
      private Stack<Command> undoCommandStack = new Stack<>();
      //记录操作前和操作后
      private Stack<Memento[]> undoMementoStack = new Stack<>();
      //撤销的历史记录
      private Stack<Command> redoCommandStack = new Stack<>();
      //记录撤销前和撤销后
      private Stack<Memento[]> redoMenentoStack = new Stack<>();
    
      public void setCommand(Command command) {
        this.command = command;
      }
    
      public void editText() {
        Memento m1 = command.createMemento();
        command.execute();
        Memento m2 = command.createMemento();
        undoCommandStack.push(command);
        undoMementoStack.push(new Memento[]{m1, m2});
      }
    
      /**
       * 撤销功能
       */
      public void undoText() {
        if (!undoCommandStack.isEmpty()) {
          Command command = undoCommandStack.pop();
          Memento[] mementos = undoMementoStack.pop();
          command.undo(mementos[0]);
          redoCommandStack.push(command);
          redoMenentoStack.push(mementos);
        }
      }
    
      /**
       * 恢复功能
       */
      public void redoText() {
        if (!redoCommandStack.isEmpty()) {
          Command command = redoCommandStack.pop();
          Memento[] mementos = redoMenentoStack.pop();
          command.undo(mementos[1]);
          undoCommandStack.push(command);
          undoMementoStack.push(mementos);
        }
      }
    }
    

    内部保存命令执行的历史记录、命令执行前及执行后的备忘录的历史记录(可撤销的列表)和撤销执行的历史记录、
    撤销前及撤销后的备忘录的历史记录(可恢复的列表),有撤销才会有恢复,文本编辑器相当于备忘录模式中的备忘录管理者角色。

    客户端使用

    public class Client {
    
      public static void main(String[] args) {
        //组装命令和执行者
        Receiver receiver = new Receiver();
        TextEditor textEditor = new TextEditor();
        //追加hello
        textEditor.setCommand(new AppendCommand(receiver, "hello"));
        textEditor.editText();
        //追加world
        textEditor.setCommand(new AppendCommand(receiver, "world"));
        textEditor.editText();
        //删除orld
        textEditor.setCommand(new RemoveCommand(receiver, "orld"));
        textEditor.editText();
        //撤销
        textEditor.undoText();
        //撤销
        textEditor.undoText();
        //恢复
        textEditor.redoText();
      }
    
    }
    

    备忘录模式在Spring Webflow中的实现

    Spring Webflow 构建于SpringMVC之上,允许实现Web应用程序的"流程",适用于加班单申请,办理登机手续等有状态的流程。

    引入maven依赖

    <dependency>
      <groupId>org.springframework.webflow</groupId>
      <artifactId>spring-webflow</artifactId>
      <version>2.5.1.RELEASE</version>
    </dependency>
    

    示例

    Spring Webflow中的StateManageableMessageContext

    /**
     * A message context whose internal state can be managed by an external care-taker. State management employs the GOF
     * Memento pattern. This context can produce a serializable memento representing its internal state at any time. A
     * care-taker can then use that memento at a later time to restore any context instance to a previous state.
     * 
     * @author Keith Donald
     */
    public interface StateManageableMessageContext extends MessageContext {
    
    	/**
    	 * Create a serializable memento, or token representing a snapshot of the internal state of this message context.
    	 * @return the messages memento
    	 */
    	Serializable createMessagesMemento();
    
    	/**
    	 * Set the state of this context from the memento provided. After this call, the messages in this context will match
    	 * what is encapsulated inside the memento. Any previous state will be overridden.
    	 * @param messagesMemento the messages memento
    	 */
    	void restoreMessages(Serializable messagesMemento);
    
    	/**
    	 * Configure the message source used to resolve messages added to this context. May be set at any time to change how
    	 * coded messages are resolved.
    	 * @param messageSource the message source
    	 * @see MessageContext#addMessage(MessageResolver)
    	 */
    	void setMessageSource(MessageSource messageSource);
    }
    

    实现类如下

    /**
     * The default message context implementation. Uses a {@link MessageSource} to resolve messages that are added by
     * callers.
     *
     * @author Keith Donald
     */
    public class DefaultMessageContext implements StateManageableMessageContext {
    	// implementing state manageable message context
    
    	public Serializable createMessagesMemento() {
    		return new LinkedHashMap<Object, List<Message>>(sourceMessages);
    	}
    
    	@SuppressWarnings("unchecked")
    	public void restoreMessages(Serializable messagesMemento) {
    		sourceMessages.putAll((Map<Object, List<Message>>) messagesMemento);
    	}
    
    	public void setMessageSource(MessageSource messageSource) {
    		if (messageSource == null) {
    			messageSource = new DefaultTextFallbackMessageSource();
    		}
    		this.messageSource = messageSource;
    	}
    }
    

    总结

    优点

    1. 提供一种可以恢复状态的机制,可以很方便的将数据恢复到某个历史的状态。
    2. 实现了内部状态的封装,除了创建它的发起人,其他对象都不能访问这些状态信息。

    缺点

    1. 如果要保存的内部状态信息过多,将会占用比较大的内存资源。

    本质

    备忘录模式的本质是保存和恢复内部状态,保存是手段,恢复才是目的。

    使用场景

    1. 需要保存一个对象在某个时刻的全部或部分状态,方便在以后需要的时候,可以将该对象恢复到先前的状态。

    参考

    大战设计模式【22】—— 备忘录模式
    设计模式的征途—20.备忘录(Memento)模式
    设计模式(二十)——备忘录模式(游戏角色状态恢复问题)
    备忘录模式(详解版)
    研磨设计模式-书籍

  • 相关阅读:
    golang协程进行同步方法
    golang实现任务分发处理
    nginx lua获取客户端ip
    Ubuntu14.04手动创建桌面快捷方式
    SCRIPT1010: 缺少标识符 常见原因
    ubuntu下 mysql5.6.4 +sphinx安装
    bonobo server: git clone fatal: early EOF
    VS调试时不捕捉Exception
    PPC Windows Mobile判断网络(gprs)连接与否代码
    windows mobile 开发:让GPS一直在待机模式下也能运行
  • 原文地址:https://www.cnblogs.com/strongmore/p/15240103.html
Copyright © 2020-2023  润新知