备忘录模式

备忘录模式是一种软件设计模式,可以将一个物件内部私有的状态公开。可以使用此模式的一个例子是将物件回复到其先前的状态(撤消变更),还有的例子是版本控制,以及自定义序列化。

备忘录模式主要由三个角色来实现:发起者、管理员和备忘录。发起者是具有内部状态的角色。管理员可以对发起起者做一些操作,尤其是希望能够撤消变更的操作。竹管理员首先向发起者索要一件备忘录。然后它执行此操作(或操作序列)。为了可以回滚到操作之前的状态,它将备忘录返回给发起者。备忘录本身是一个不透明的物件(管理员不能或不应更改此物件)。使用此模式时,应注意发起者是否可能更改其他物件或资源——备忘录模式是对单一物件进行操作。

备忘录模式的经典范例有伪随机数生成器(PRNG)(每个消费者(管理员)在初始化伪随机数生成器(发起者)时,若使用相同的种子值(备忘录),可以产生相同的乱数序列),此外,还有有限状态机及其状态。

概述

备忘录设计模式是著名的《设计模式:可复用面向对象软件的基础》所收录的二十三个之一。此书描述了如何解决常见的设计问题,以设计出弹性且异重用的面向对象软件(亦即更容易实现、更改、测试、维护和重用的物件)。备忘录模式是由 Noah Thompson、David Espiritu 和 Drew Clinkenbeard 博士为惠普早期产品创建的。

备忘录设计模式可以解决什么问题?

  • 物件的内部状态须要保存在外部,以便之后可将物件回复到此状态。
  • 不违反物件的封装性。

问题在于,一个良好设计的物件会被封装起来,其表示(数据结构)是隐藏在物件内部,且无法从外部存取。

备忘录设计模式描述了什么解决方案?

让一个物件(发起者)本身负责

  • 将其内部状态保存到(备忘录)物件,并且
  • 从(备忘录)物件回复到以前的状态。

只有产生此备忘录的发起者可以存取它。

客户端(管理员)可以向发起者请求备忘录(以保存发起者的内部状态)并将备忘录传递回发起者(以恢复到先前的状态)。

这使得外部能够在不破坏其封装的情况下,保存和回复发起者的内部状态。

请参考下面的 UML 类别图和序列图。

结构

UML 类别图和序列图

 
备忘录设计模式的 UML 类别图和序列图。 [1]

在上面的UML类图中, Caretaker 类别指向向 Originator 类别,用于保存( createMemento() )和恢复( restore(memento) )始发者的内部状态。Originator 类别实现了(1) createMemento() ,借由创建并返回 Memento 物件,其记录了发起者当前内部状态,(2) restore(memento),借由将 Memento 物件传入,以回复状态。

UML序列图显示了运行时交动:(1) 保存发起者的内部状态: Caretaker对象在Originator对象上调用createMemento() ,创建一个Memento对象,保存其当前的内部状态 ( setState() ),并将Memento返回给Caretaker 。(2) 恢复Originator的内部状态: CaretakerOriginator对象调用restore(memento) ,并指定存储应恢复状态的Memento对象。 OriginatorMemento获取状态 ( getState() ) 以设置自己的状态。

Java 范例

以下的 Java 程序说明了备忘录模式的“撤消变更”用法。

import java.util.List;
import java.util.ArrayList;
class Originator {
    private String state;
    // 這個類別可以包含其它不用存在 State 中的備忘錄

    public void set(String state) {
        this.state = state;
        System.out.println("Originator: Setting state to " + state);
    }
 
    public Memento saveToMemento() {
        System.out.println("Originator: Saving to Memento.");
        return new Memento(this.state);
    }
 
    public void restoreFromMemento(Memento memento) {
        this.state = memento.getSavedState();
        System.out.println("Originator: State after restoring from Memento: " + state);
    }
 
    public static class Memento {
        private final String state;

        public Memento(String stateToSave) {
            state = stateToSave;
        }
 
        // accessible by outer class only
        private String getSavedState() {
            return state;
        }
    }
}
 
class Caretaker {
    public static void main(String[] args) {
        List<Originator.Memento> savedStates = new ArrayList<Originator.Memento>();
 
        Originator originator = new Originator();
        originator.set("State1");
        originator.set("State2");
        savedStates.add(originator.saveToMemento());
        originator.set("State3");
        // 我們可以要求多個備忘錄,並選擇要回復到那個狀態。
        savedStates.add(originator.saveToMemento());
        originator.set("State4");
 
        originator.restoreFromMemento(savedStates.get(1));   
    }
}

输出是:

Originator: Setting state to State1
Originator: Setting state to State2
Originator: Saving to Memento.
Originator: Setting state to State3
Originator: Saving to Memento.
Originator: Setting state to State4
Originator: State after restoring from Memento: State3

本示例使用 String 作为状态,它是 Java 中的不可变物件。在现实生活中,状态几乎总是一个可变物件,在这种情况下,必须创建状态的副本。

必须指出的是,所展示的实现有一个缺点:它声明了一个内部类。如果备忘录可以适用于多个发起者,则会更好。

实现Memento 还有其他三种主要方式:

  1. 序列化。
  2. 在同一个包中声明的类别。
  3. 还可以通过代理来访问该物件,代理可以实现对物件的任何保存/恢复操作。

C# 示例

备忘录模式允许人们在不违反封装的情况下捕获物件的内部状态,以便以后可以根据需求来撤消变更/回复变更。从这里可以看出,备忘录物件实际上用于恢复物件中所做的更改。

class Memento
{
    private readonly string savedState;

    private Memento(string stateToSave)
    {
        savedState = stateToSave;
    }

    public class Originator
    {
        private string state;
        // The class could also contain additional data that is not part of the
        // state saved in the memento.

        public void Set(string state)
        {
            Console.WriteLine("Originator: Setting state to " + state);
            this.state = state;
        }

        public Memento SaveToMemento()
        {
            Console.WriteLine("Originator: Saving to Memento.");
            return new Memento(state);
        }

        public void RestoreFromMemento(Memento memento)
        {
            state = memento.savedState;
            Console.WriteLine("Originator: State after restoring from Memento: " + state);
        }
    }
}

class Caretaker
{
    static void Main(string[] args)
    {
        var savedStates = new List<Memento>();

        var originator = new Memento.Originator();
        originator.Set("State1");
        originator.Set("State2");
        savedStates.Add(originator.SaveToMemento());
        originator.Set("State3");
        // We can request multiple mementos, and choose which one to roll back to.
        savedStates.Add(originator.SaveToMemento());
        originator.Set("State4");

        originator.RestoreFromMemento(savedStates[1]);
    }
}

Python 范例

"""
備忘錄模式範例
"""


class Originator:
    _state = ""

    def set(self, state: str) -> None:
        print(f"Originator: Setting state to {state}")
        self._state = state

    def save_to_memento(self) -> "Memento":
        return self.Memento(self._state)

    def restore_from_memento(self, m: "Memento") -> None:
        self._state = m.get_saved_state()
        print(f"Originator: State after restoring from Memento: {self._state}")

    class Memento:

        def __init__(self, state):
            self._state = state

        def get_saved_state(self):
            return self._state


saved_states = []
originator = Originator()
originator.set("State1")
originator.set("State2")
saved_states.append(originator.save_to_memento())

originator.set("State3")
saved_states.append(originator.save_to_memento())

originator.set("State4")

originator.restore_from_memento(saved_states[1])

JavaScript 范列

// The Memento pattern is used to save and restore the state of an object.
// A memento is a snapshot of an object's state.
var Memento = {// Namespace: Memento
    savedState : null, // The saved state of the object.

    save : function(state) { // Save the state of an object.
        this.savedState = state;
    },

    restore : function() { // Restore the state of an object.
        return this.savedState;
    }
};

// The Originator is the object that creates the memento.
// defines a method for saving the state inside a memento.
var Originator = {// Namespace: Originator
        state : null, // The state to be stored

        // Creates a new originator with an initial state of null
        createMemento : function() { 
            return {
                state : this.state // The state is copied to the memento.
            };
        },
        setMemento : function(memento) { // Sets the state of the originator from a memento
            this.state = memento.state;
        }
    };


// The Caretaker stores mementos of the objects and
// provides operations to retrieve them.
var Caretaker = {// Namespace: Caretaker
        mementos : [], // The mementos of the objects.
        addMemento : function(memento) { // Add a memento to the collection.
            this.mementos.push(memento);
        },
        getMemento : function(index) { // Get a memento from the collection.
            return this.mementos[index];
        }
    };

var action_step = "Foo"; // The action to be executed/the object state to be stored.
var action_step_2 = "Bar"; // The action to be executed/the object state to be stored.

// 設置初始值
Originator.state = action_step;
Caretaker.addMemento(Originator.createMemento());// save the state to the history
console.log("Initial State: " + Originator.state); // Foo

// 改變初始值
Originator.state = action_step_2;
Caretaker.addMemento(Originator.createMemento()); // save the state to the history
console.log("State After Change: " + Originator.state); // Bar

// 回復到第一個狀態——撤消變更
Originator.setMemento(Caretaker.getMemento(0));
console.log("State After Undo: " + Originator.state); // Foo

// 回復到第二個狀態——重做變更
Originator.setMemento(Caretaker.getMemento(1));
console.log("State After Redo: " + Originator.state); // Bar

参考