软件设计模式学习(二十二)备忘录模式

备忘录模式提供了一种对象状态的撤销实现机制,当系统中某一对象需要恢复到某一历史状态时可以使用备忘录模式来进行设计

模式动机

人人都有后悔的时候,在软件使用过程中难免会出现一些误操作,如不小心删除了某些文字或图片,数据填入错误等,对于这些误操作,需要提供一种后悔药机制,让系统可以回到误操作前的状态,这就是备忘录模式的模式动机

模式定义

在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。备忘录模式是一种对象行为型模式,其别名为 Token

模式结构

  1. Originator(原发器)

    原发器可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态。一般将需要保存内部状态的类设计为原发器。

  2. Memento(备忘录)

    存储原发器的内部状态,根据原发器来决定保存哪些内部状态。需要注意的是,除了原发器本身与负责人之外,备忘录对象不能直接供其他类使用。

  3. Caretaker(负责人)

    负责人又称管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。

模式分析

前面已经说了,备忘录模式就是用来吃后悔药的,理解起来并不难,关键在于如何设计备忘录类和负责人类。

备忘录中存储的是原发器的中间状态,因此需要防止原发器以外的其他对象访问备忘录。也不能在备忘录对象之外保存原发器状态,如果暴露其内部状态将违反封装的原则。

为了实现对备忘录对象的封装,需要对备忘录的调用进行控制。对于原发器而言,它可以调用备忘录的所有信息,允许原发器访问先前状态的所有数据。对于负责人而言,只负责备忘录的保存并将备忘录传递给其他对象。对于其他对象而言,只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存状态。

下面通过一个实例来进一步理解备忘录模式。

模式实例

某系统提供了用户信息操作模块,用户可以修改自己的各项信息,用户在进行了错误操作后可以恢复到操作之前的状态。

  1. 原发器 UserInfoDTO(用户信息类)

    package dp.memento;
    
    public class UserInfoDTO {
    
        private String account;
        private String password;
    
        public String getAccount() {
            return account;
        }
    
        public void setAccount(String account) {
            this.account = account;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    	
        //	创建一个备忘录对象
        public Memento saveMemento() {
            return new Memento(account, password);
        }
    	
        //	根据备忘录对象恢复原发器状态
        public void restoreMemento(Memento memento) {
            this.account = memento.getAccount();
            this.password = memento.getPassword();
        }
    
        public void show() {
            System.out.println("Account: " + this.account);
            System.out.println("Password: " + this.getPassword());
        }
    }
    
  2. 备忘录 Memento

    设计备忘录时需要考虑到封装性,即除了原发器类,不允许其他类来调用其构造函数与相关方法。一般将备忘录类和原发器类定义在同一包中来实现封装,使用默认访问标识符来定义备忘录类,即保证包内可见性。

    package dp.memento;
    
    class Memento {
    
        private String account;
        private String password;
    
        public Memento(String account, String password) {
            this.account = account;
            this.password = password;
        }
    
        public String getAccount() {
            return account;
        }
    
        public void setAccount(String account) {
            this.account = account;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    
  3. 负责人 Caretaker

    package dp.memento;
    
    public class Caretaker {
    
        private Memento memento;
    
        public Memento getMemento() {
            return memento;
        }
    
        public void setMemento(Memento memento) {
            this.memento = memento;
        }
    }
    
  4. 客户端测试类 Client

    import dp.memento.UserInfoDTO;
    import dp.memento.Caretaker;
    
    public class Client {
    
        public static void main(String[] args) {
    
            UserInfoDTO user = new UserInfoDTO();
    		
            // 创建负责人
            Caretaker caretaker = new Caretaker();
    
            user.setAccount("zhangsan");
            user.setPassword("123456");
            System.out.println("状态一");
            user.show();
            
    		// 保存备忘录
            caretaker.setMemento(user.saveMemento());
            System.out.println("------------------------");
    
            user.setPassword("11111111");
            System.out.println("状态二");
            user.show();
            System.out.println("------------------------");
    
            // 从备忘录中恢复
            user.restoreMemento(caretaker.getMemento());
            System.out.println("回到状态一");
            user.show();
            System.out.println("------------------------");
        }
    }
    
  5. 运行结果

模式优缺点

备忘录模式优点:

  • 提供了一种状态恢复的实现机制,使得用户可以方便地回到特定的一个历史步骤
  • 实现了信息的封装,备忘录只保存原发器的状态,不会被其他代码修改。采用堆栈来存储备忘录对象可以实现多次撤销操作,可以通过在负责人中定义集合对象来存储多个备忘录。

备忘录模式缺点:

  • 每保存一次对象的状态都需要消耗内存资源,资源消耗过大

模式适用环境

在以下情况可以使用备忘录模式:

  • 保存一个对象在某一时刻的状态或部分状态
  • 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏封装性,一个对象不希望外界直接访问得到其内部状态,通过负责人可以间接访问其内部状态