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

1. 说明

备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不暴露对象内部状态的情况下捕获并恢复对象之前的状态。备忘录模式涉及三个主要角色:原发器(Originator)、备忘录(Memento)和负责人(Caretaker)。

以下是备忘录模式的关键组成部分和说明:

  1. 原发器(Originator):原发器是需要保存状态的对象。它具有一个方法来创建备忘录以保存其内部状态,并具有另一个方法来从备忘录中恢复状态。原发器通常包括了需要保存和恢复的状态数据。
  2. 备忘录(Memento):备忘录是原发器的状态快照。它包含了原发器的一部分或全部状态信息。备忘录对象不会被修改,只能创建和读取。备忘录通常提供一些方法来获取状态信息。
  3. 负责人(Caretaker):负责人是用于存储备忘录的对象,它不应该修改备忘录的内容。负责人负责保持备忘录的历史记录,并可以在需要时将状态从备忘录中恢复到原发器。

备忘录模式的主要目的是允许对象保存其内部状态的历史记录,以便可以随时将其还原到先前的状态。这在需要撤销操作或实现历史记录功能时非常有用。

2. 使用的场景

备忘录模式通常在以下情况下被使用:

  1. 需要实现撤销功能: 当你需要允许用户撤销操作,即将对象恢复到之前的状态时,备忘录模式非常有用。例如,文本编辑器中的撤销和重做功能。
  2. 需要保存对象历史状态: 当你需要跟踪和保存对象的历史状态,以便在需要时进行审计或还原时,备忘录模式可以派上用场。这在游戏中的存档和恢复、数据库事务管理等方面很常见。
  3. 需要实现快照和恢复功能: 当你需要在对象状态的不同时间点创建快照(备忘录),以便将对象恢复到这些时间点的状态时,备忘录模式是一个好选择。这在虚拟机快照、系统恢复点等场景中有用。
  4. 需要保持封装性: 备忘录模式允许对象在不暴露其内部状态的情况下保存和恢复状态,从而保持了对象的封装性。这对于保持代码的可维护性和可扩展性非常重要。
  5. 需要实现多级撤销和重做: 在某些应用中,需要实现多级的撤销和重做功能,备忘录模式可以帮助管理和跟踪状态的历史记录。

总之,备忘录模式适用于需要保存和恢复对象状态的情况,以及需要实现撤销、历史记录、快照和恢复等功能的场景。它有助于保持代码的整洁性和可维护性,同时提供了灵活性来处理对象状态的变化。

3. 应用例子

以下是使用 Python 实现备忘录模式的示例,假设我们有一个文本编辑器,需要实现撤销和重做功能:

# 备忘录类
class EditorMemento:
    def __init__(self, content):
        self._content = content

    def get_content(self):
        return self._content

# 原发器类
class TextEditor:
    def __init__(self):
        self._content = ""

    def type(self, text):
        self._content += text

    def get_content(self):
        return self._content

    def save(self):
        return EditorMemento(self._content)

    def restore(self, memento):
        self._content = memento.get_content()

# 负责人类
class History:
    def __init__(self):
        self._mementos = []

    def push(self, memento):
        self._mementos.append(memento)

    def pop(self):
        if self._mementos:
            return self._mementos.pop()
        return None

if __name__ == "__main__":
    editor = TextEditor()
    history = History()

    editor.type("Hello, ")
    history.push(editor.save())

    editor.type("world!")
    history.push(editor.save())

    print("Current Content:", editor.get_content())  # 输出 "Hello, world!"

    editor.type(" This is a test.")
    history.push(editor.save())

    print("Current Content:", editor.get_content())  # 输出 "Hello, world! This is a test."

    # 撤销一步
    history.pop()
    editor.restore(history.pop())

    print("After Undo:", editor.get_content())  # 输出 "Hello, world!"

    # 重做一步
    editor.restore(history.pop())

    print("After Redo:", editor.get_content())  # 输出 "Hello, world! This is a test."

在这个示例中:

  • EditorMemento 是备忘录类,用于存储文本编辑器的状态(文本内容)。
  • TextEditor 是原发器类,代表文本编辑器,它可以输入文本、获取当前内容、保存内容到备忘录,以及从备忘录中恢复内容。
  • History 是负责人类,负责管理备忘录对象的历史记录,实现了撤销和重做操作。

在客户端代码中,我们创建了一个文本编辑器和一个历史记录对象。我们在编辑器中输入文本,通过保存内容到备忘录并将备忘录压入历史记录中来记录不同的状态。然后,我们演示了撤销和重做操作,通过还原备忘录中的内容,可以实现撤销和重做功能。这个示例展示了备忘录模式用于实现撤销/重做功能的应用。

4. 实现要素

备忘录模式的实现要素包括以下几个部分:

  1. 原发器(Originator):原发器是需要保存状态的对象。它具有以下关键特征:
  • 包含需要保存的内部状态。
  • 能够创建备忘录对象并将当前状态保存到备忘录中。
  • 能够从备忘录中恢复状态,将备忘录中的状态还原到原发器中。
  1. 备忘录(Memento):备忘录是原发器状态的快照。备忘录具有以下特征:
  • 包含原发器的一部分或全部状态信息。
  • 不会被外部对象修改,只能被创建和读取。
  • 提供方法来获取状态信息。
  1. 负责人(Caretaker):负责人是用于存储备忘录的对象,它不应该修改备忘录的内容。它具有以下特征:
  • 负责管理备忘录对象的存储,通常使用列表或堆栈等数据结构。
  • 可以保存多个备忘录,形成状态的历史记录。
  • 可以在需要时将状态从备忘录中恢复到原发器。

5. Java/golang/javascrip/C++ 等语言实现方式

5.1 Java实现

上述例子用Java语言实现示例如下:

import java.util.ArrayList;
import java.util.List;

// 备忘录类
class EditorMemento {
    private final String content;

    public EditorMemento(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

// 原发器类
class TextEditor {
    private String content;

    public TextEditor() {
        this.content = "";
    }

    public void type(String text) {
        content += text;
    }

    public String getContent() {
        return content;
    }

    public EditorMemento save() {
        return new EditorMemento(content);
    }

    public void restore(EditorMemento memento) {
        content = memento.getContent();
    }
}

// 负责人类
class History {
    private final List<EditorMemento> mementos = new ArrayList<>();

    public void push(EditorMemento memento) {
        mementos.add(memento);
    }

    public EditorMemento pop() {
        if (!mementos.isEmpty()) {
            int lastIndex = mementos.size() - 1;
            EditorMemento memento = mementos.get(lastIndex);
            mementos.remove(lastIndex);
            return memento;
        }
        return null;
    }
}

public class Main {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        History history = new History();

        editor.type("Hello, ");
        history.push(editor.save());

        editor.type("world!");
        history.push(editor.save());

        System.out.println("Current Content: " + editor.getContent());  // 输出 "Hello, world!"

        editor.type(" This is a test.");
        history.push(editor.save());

        System.out.println("Current Content: " + editor.getContent());  // 输出 "Hello, world! This is a test."

        // 撤销一步
        history.pop();
        editor.restore(history.pop());

        System.out.println("After Undo: " + editor.getContent());  // 输出 "Hello, world!"

        // 重做一步
        editor.restore(history.pop());

        System.out.println("After Redo: " + editor.getContent());  // 输出 "Hello, world! This is a test."
    }
}

在这个 Java 示例中,我们创建了与 Python 示例相似的类结构。我们使用 TextEditor 类来表示文本编辑器,EditorMemento 用于保存状态快照,History 负责管理备忘录历史记录。然后,我们演示了输入文本、保存状态、撤销和重做的功能。这个示例展示了备忘录模式的应用,用于实现文本编辑器的撤销和重做功能。

5.2 Golang实现

上述例子用golang实现示例如下:

package main

import (
        "fmt"
)

// 备忘录类
type EditorMemento struct {
        content string
}

func NewEditorMemento(content string) *EditorMemento {
        return &EditorMemento{content}
}

func (m *EditorMemento) GetContent() string {
        return m.content
}

// 原发器类
type TextEditor struct {
        content string
}

func NewTextEditor() *TextEditor {
        return &TextEditor{content: ""}
}

func (e *TextEditor) Type(text string) {
        e.content += text
}

func (e *TextEditor) GetContent() string {
        return e.content
}

func (e *TextEditor) Save() *EditorMemento {
        return NewEditorMemento(e.content)
}

func (e *TextEditor) Restore(m *EditorMemento) {
        e.content = m.GetContent()
}

// 负责人类
type History struct {
        mementos []*EditorMemento
}

func NewHistory() *History {
        return &History{mementos: make([]*EditorMemento, 0)}
}

func (h *History) Push(m *EditorMemento) {
        h.mementos = append(h.mementos, m)
}

func (h *History) Pop() *EditorMemento {
        if len(h.mementos) > 0 {
                lastIndex := len(h.mementos) - 1
                m := h.mementos[lastIndex]
                h.mementos = h.mementos[:lastIndex]
                return m
        }
        return nil
}

func main() {
        editor := NewTextEditor()
        history := NewHistory()

        editor.Type("Hello, ")
        history.Push(editor.Save())

        editor.Type("world!")
        history.Push(editor.Save())

        fmt.Println("Current Content:", editor.GetContent()) // 输出 "Hello, world!"

        editor.Type(" This is a test.")
        history.Push(editor.Save())

        fmt.Println("Current Content:", editor.GetContent()) // 输出 "Hello, world! This is a test."

        // 撤销一步
        history.Pop()
        editor.Restore(history.Pop())

        fmt.Println("After Undo:", editor.GetContent()) // 输出 "Hello, world!"

        // 重做一步
        editor.Restore(history.Pop())

        fmt.Println("After Redo:", editor.GetContent()) // 输出 "Hello, world! This is a test."
}

这个 Go 示例中,我们创建了与之前示例相同的类结构,包括 TextEditor、EditorMemento 和 History。我们使用这些类来实现文本编辑器的撤销和重做功能,以及保存和还原状态。这个示例展示了备忘录模式在 Go 中的应用。

5.3 Javascript实现

上述例子用javascript实现示例如下:

// 备忘录类
class EditorMemento {
    constructor(content) {
        this.content = content;
    }

    getContent() {
        return this.content;
    }
}

// 原发器类
class TextEditor {
    constructor() {
        this.content = "";
    }

    type(text) {
        this.content += text;
    }

    getContent() {
        return this.content;
    }

    save() {
        return new EditorMemento(this.content);
    }

    restore(memento) {
        this.content = memento.getContent();
    }
}

// 负责人类
class History {
    constructor() {
        this.mementos = [];
    }

    push(memento) {
        this.mementos.push(memento);
    }

    pop() {
        if (this.mementos.length > 0) {
            return this.mementos.pop();
        }
        return null;
    }
}

// 示例
const editor = new TextEditor();
const history = new History();

editor.type("Hello, ");
history.push(editor.save());

editor.type("world!");
history.push(editor.save());

console.log("Current Content:", editor.getContent()); // 输出 "Hello, world!"

editor.type(" This is a test.");
history.push(editor.save());

console.log("Current Content:", editor.getContent()); // 输出 "Hello, world! This is a test."

// 撤销一步
history.pop();
editor.restore(history.pop());

console.log("After Undo:", editor.getContent()); // 输出 "Hello, world!"

// 重做一步
editor.restore(history.pop());

console.log("After Redo:", editor.getContent()); // 输出 "Hello, world! This is a test."

在这个 JavaScript 示例中,我们创建了与之前示例相同的类结构,包括 TextEditor、EditorMemento 和 History。我们使用这些类来实现文本编辑器的撤销和重做功能,以及保存和还原状态。这个示例展示了备忘录模式在 JavaScript 中的应用。

5.4 C++实现

上述例子用C++实现如下:

#include <iostream>
#include <vector>

// 备忘录类
class EditorMemento {
public:
    EditorMemento(const std::string& content) : content(content) {}

    std::string getContent() const {
        return content;
    }

private:
    std::string content;
};

// 原发器类
class TextEditor {
public:
    TextEditor() : content("") {}

    void type(const std::string& text) {
        content += text;
    }

    std::string getContent() const {
        return content;
    }

    EditorMemento save() const {
        return EditorMemento(content);
    }

    void restore(const EditorMemento& memento) {
        content = memento.getContent();
    }

private:
    std::string content;
};

// 负责人类
class History {
public:
    void push(const EditorMemento& memento) {
        mementos.push_back(memento);
    }

    EditorMemento pop() {
        if (!mementos.empty()) {
            EditorMemento memento = mementos.back();
            mementos.pop_back();
            return memento;
        }
        return EditorMemento("");
    }

private:
    std::vector<EditorMemento> mementos;
};

int main() {
    TextEditor editor;
    History history;

    editor.type("Hello, ");
    history.push(editor.save());

    editor.type("world!");
    history.push(editor.save());

    std::cout << "Current Content: " << editor.getContent() << std::endl; // 输出 "Hello, world!"

    editor.type(" This is a test.");
    history.push(editor.save());

    std::cout << "Current Content: " << editor.getContent() << std::endl; // 输出 "Hello, world! This is a test."

    // 撤销一步
    history.pop();
    editor.restore(history.pop());

    std::cout << "After Undo: " << editor.getContent() << std::endl; // 输出 "Hello, world!"

    // 重做一步
    editor.restore(history.pop());

    std::cout << "After Redo: " << editor.getContent() << std::endl; // 输出 "Hello, world! This is a test."

    return 0;
}

在这个 C++ 示例中,我们创建了与之前示例相同的类结构,包括 TextEditor、EditorMemento 和 History。我们使用这些类来实现文本编辑器的撤销和重做功能,以及保存和还原状态。这个示例展示了备忘录模式在 C++ 中的应用。

6. 练习题

考虑一个简单的电子游戏,玩家可以在游戏中完成不同的关卡。玩家可以在任何时间保存游戏进度并在需要时加载保存的进度。使用备忘录模式实现这个游戏的进度保存和恢复功能。

要求:

  1. 创建一个 Game 类,该类表示游戏,具有以下方法:
  • playLevel(level: string): void:玩家玩游戏的特定关卡。
  • saveProgress(): GameMemento:保存当前游戏进度并返回一个备忘录对象。
  • restoreProgress(memento: GameMemento): void:从备忘录对象中恢复游戏进度。
  1. 创建一个 GameMemento 类,表示游戏进度的备忘录。该类应具有以下属性和方法:
  • level: string:保存的游戏关卡。
  • constructor(level: string):构造函数,用于创建备忘录对象。
  1. 创建一个 ProgressManager 类,该类充当负责人,负责管理游戏进度的保存和恢复。该类应具有以下方法:
  • pushMemento(memento: GameMemento): void:将备忘录对象推入进度管理器。
  • popMemento(): GameMemento:从进度管理器中弹出备忘录对象。

编写一个程序,模拟玩家玩游戏的过程,保存进度并在需要时恢复进度。确保游戏可以正确地保存和恢复进度。

你可以在评论区里或者私信我回复您的答案,这样我或者大家都能帮你解答,期待着你的回复~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guohuang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值