Spring + 设计模式 (二十三) 行为型 - 备忘录模式

54 阅读8分钟

备忘录模式

引言

备忘录模式是一种行为型设计模式,用于在不破坏对象封装性的前提下,捕获并保存对象的内部状态,以便在未来恢复到该状态。想象一个文本编辑器,支持“撤销”功能,每次编辑操作都会保存当前文档状态,允许用户回滚到之前的版本。备忘录模式的核心思想是将对象的状态存储在一个外部“备忘录”对象中,并通过“管理者”控制状态的保存与恢复。它在保证封装性的同时,提供了灵活的状态管理机制,广泛应用于需要历史记录、撤销重做或状态快照的场景。它的魅力在于让状态管理既安全又高效,完美平衡了灵活性与封装性。

实际开发中的用途

备忘录模式在实际开发中适用于需要保存和恢复对象状态的场景,尤其在以下场景中表现出色:

  • 撤销/重做功能:如文本编辑器、图形编辑软件或 IDE,支持用户回滚操作。
  • 事务管理:在数据库操作或业务流程中,保存中间状态以便在失败时回滚。
  • 游戏开发:保存玩家的游戏进度(如关卡、分数)以便恢复。
  • 配置管理:在系统配置修改时,保存旧配置以便在出错时恢复。

以电商系统的购物车为例,假设用户在添加商品到购物车后可能需要撤销操作。如果直接在购物车对象中维护状态历史,代码会变得复杂且违背单一职责原则。使用备忘录模式,可以将购物车的状态(如商品列表、数量)保存到备忘录对象中,由一个管理者(如 CartHistory)负责状态的存储与恢复。这种方式不仅解耦了状态管理逻辑,还便于扩展(如支持多级撤销)。备忘录模式的核心价值在于提供了一种优雅的状态管理方式,让系统更具灵活性和用户友好性。

示例场景:一个在线文档编辑系统,用户编辑文档后可撤销到上一次保存的状态。每次编辑操作都会生成一个备忘录,保存当前的文档内容,撤销时直接恢复到指定状态,逻辑清晰且用户体验流畅。

Spring 源码中的应用

在 Spring 框架中,备忘录模式的典型应用体现在 Spring WebFluxWebSession 管理中。WebSession 用于在 Web 应用中保存用户会话状态,允许在请求间持久化数据(如用户偏好、临时数据)。Spring WebFlux 使用类似备忘录模式的方式,通过 WebSession 保存会话状态的快照,并在需要时恢复。

源码分析

以下是 Spring WebFlux 中 WebSession 的相关源码片段,摘自 org.springframework.web.server.session 包中的 DefaultWebSessionManager

public class DefaultWebSessionManager implements WebSessionManager {

    private WebSessionStore sessionStore = new InMemoryWebSessionStore();

    @Override
    public Mono<WebSession> getSession(ServerWebExchange exchange) {
        return Mono.defer(() -> {
            // 从存储中获取或创建会话
            String sessionId = resolveSessionId(exchange);
            return (sessionId != null ? 
                    this.sessionStore.retrieveSession(sessionId) : 
                    this.sessionStore.createWebSession())
                    .doOnNext(session -> exchange.getResponse().beforeCommit(() -> 
                        saveSession(session, exchange)));
        });
    }

    private Mono<Void> saveSession(WebSession session, ServerWebExchange exchange) {
        // 保存会话状态到存储
        if (!session.isStarted() || session.isExpired()) {
            return Mono.empty();
        }
        return this.sessionStore.updateSession(session);
    }
}

分析

  • 备忘录角色WebSession 充当备忘录,存储会话的当前状态(如属性键值对)。
  • 发起人角色DefaultWebSessionManager 作为发起人,负责创建和恢复会话状态。
  • 管理者角色WebSessionStore(如 InMemoryWebSessionStore)作为管理者,负责存储和检索备忘录对象。
  • 模式体现WebSession 通过 getSession 获取会话状态,并在请求结束时通过 saveSession 保存状态快照。状态的存储和恢复完全由 WebSessionStore 管理,WebSession 本身无需关心存储细节,保证了封装性。
  • 问题解决:该机制解耦了会话状态的管理与存储逻辑,支持分布式会话(如通过 Redis 存储)和动态恢复(如用户重新登录后恢复会话)。它广泛应用于 Spring WebFlux 的无状态 Web 应用中。

这种设计让 Spring WebFlux 在处理高并发 Web 请求时,既能高效管理会话状态,又能保证状态的隔离性和安全性,体现了备忘录模式在企业级场景中的强大能力。

Spring Boot 代码案例

以下是一个基于 Spring Boot 的案例,模拟一个在线文档编辑系统,通过备忘录模式实现文档的编辑与撤销功能。案例展示了如何利用备忘录模式保存和恢复文档状态,解决状态管理的复杂性。

案例背景

在一个在线文档编辑系统中,用户可以编辑文档内容,并支持撤销操作。每次编辑都会保存文档的当前状态,撤销时恢复到上一次的状态。

代码实现

// 备忘录类,保存文档状态
public class DocumentMemento {
    private final String content;

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

    public String getContent() {
        return content;
    }
}

// 发起人类,管理文档内容
public class DocumentEditor {
    private String content;

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

    public void edit(String newContent) {
        this.content = newContent;
    }

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

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

    public String getContent() {
        return content;
    }
}

// 管理者类,负责存储备忘录
@Service
public class DocumentHistory {
    private final Stack<DocumentMemento> history = new Stack<>();

    public void save(DocumentMemento memento) {
        history.push(memento);
    }

    public DocumentMemento undo() {
        if (!history.isEmpty()) {
            return history.pop();
        }
        return null;
    }
}

// 文档服务
@Service
public class DocumentService {
    private final DocumentEditor editor;
    private final DocumentHistory history;

    public DocumentService(DocumentEditor editor, DocumentHistory history) {
        this.editor = editor;
        this.history = history;
    }

    public void editDocument(String content) {
        // 保存当前状态
        history.save(editor.save());
        // 编辑新内容
        editor.edit(content);
    }

    public String undo() {
        DocumentMemento memento = history.undo();
        if (memento != null) {
            editor.restore(memento);
            return editor.getContent();
        }
        return editor.getContent();
    }

    public String getCurrentContent() {
        return editor.getContent();
    }
}

// Spring Boot 主应用
@SpringBootApplication
public class MementoPatternApplication {
    public static void main(String[] args) {
        SpringApplication.run(MementoPatternApplication.class, args);
    }

    @Bean
    public DocumentEditor documentEditor() {
        return new DocumentEditor();
    }
}

// 测试控制器
@RestController
@RequestMapping("/documents")
public class DocumentController {
    private final DocumentService documentService;

    public DocumentController(DocumentService documentService) {
        this.documentService = documentService;
    }

    @PostMapping("/edit")
    public String editDocument(@RequestBody Map<String, String> request) {
        String content = request.get("content");
        documentService.editDocument(content);
        return "Document updated: " + content;
    }

    @GetMapping("/undo")
    public String undo() {
        return documentService.undo();
    }

    @GetMapping("/current")
    public String getCurrentContent() {
        return documentService.getCurrentContent();
    }
}

代码说明

  • 备忘录模式体现DocumentMemento 保存文档的快照,DocumentEditor 负责创建和恢复状态,DocumentHistory 管理备忘录栈。DocumentService 协调编辑和撤销操作,利用 Spring 的依赖注入实现松耦合。
  • 优势
    • 封装性DocumentMemento 只暴露 getContent 方法,保护了状态的内部细节。
    • 低耦合:状态管理与业务逻辑分离,易于扩展(如支持多级撤销或持久化历史)。
    • 企业级适用性:结合 Spring 的 IoC 容器,代码结构清晰,适合集成到复杂系统中。
  • 运行效果:通过 POST /documents/edit 编辑文档内容,GET /documents/undo 撤销操作,GET /documents/current 查看当前内容。每次编辑都会保存状态,撤销时恢复到上一次状态。

相似的设计模式对比

备忘录模式与 命令模式(Command Pattern)在某些场景下有相似之处,特别是在支持撤销操作时。以下是对比分析:

  • 备忘录模式
    • 关键词:状态保存、恢复、封装、快照。
    • 说明:专注于捕获和恢复对象的内部状态,强调状态的封装性和隔离性。
  • 命令模式
    • 关键词:行为封装、撤销、操作记录、请求队列。
    • 说明:将操作封装为命令对象,支持执行和撤销,强调行为的封装和操作历史。

对比表格

特性备忘录模式命令模式
核心思想保存和恢复对象状态封装操作,支持执行和撤销
关注点状态快照行为封装
耦合度较低,状态与逻辑分离较低,命令与执行者分离
典型场景撤销、重做、状态恢复操作记录、事务管理、宏命令
Spring 中的实现WebSession(状态管理)TransactionTemplate(事务操作)

代码对比

以下通过一个简单文本编辑器功能对比两者的实现差异。

备忘录模式实现
// 备忘录类
public class TextMemento {
    private final String text;

    public TextMemento(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

// 编辑器类
public class TextEditor {
    private String text = "";

    public void setText(String text) {
        this.text = text;
    }

    public TextMemento save() {
        return new TextMemento(text);
    }

    public void restore(TextMemento memento) {
        this.text = memento.getText();
    }

    public String getText() {
        return text;
    }
}

// 管理者类
public class TextHistory {
    private final Stack<TextMemento> history = new Stack<>();

    public void save(TextMemento memento) {
        history.push(memento);
    }

    public TextMemento undo() {
        return history.isEmpty() ? null : history.pop();
    }
}
命令模式实现
// 命令接口
public interface Command {
    void execute();
    void undo();
}

// 具体命令
public class EditCommand implements Command {
    private final TextEditor editor;
    private final String newText;
    private String oldText;

    public EditCommand(TextEditor editor, String newText) {
        this.editor = editor;
        this.newText = newText;
    }

    @Override
    public void execute() {
        oldText = editor.getText();
        editor.setText(newText);
    }

    @Override
    public void undo() {
        editor.setText(oldText);
    }
}

// 编辑器类
public class TextEditor {
    private String text = "";

    public void setText(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

// 管理者类
public class CommandHistory {
    private final Stack<Command> history = new Stack<>();

    public void executeCommand(Command command) {
        command.execute();
        history.push(command);
    }

    public void undo() {
        if (!history.isEmpty()) {
            Command command = history.pop();
            command.undo();
        }
    }
}

差异分析

  • 备忘录模式TextMemento 只保存状态快照,TextHistory 负责存储和恢复,适合单纯的状态管理场景。状态恢复逻辑简单,但不支持复杂操作。
  • 命令模式EditCommand 封装了编辑操作和撤销逻辑,CommandHistory 管理命令执行和回滚,适合需要记录操作历史的场景。支持更复杂的撤销逻辑(如组合命令)。
  • 适用性:备忘录模式更适合状态快照管理,命令模式更适合操作记录和复杂撤销。

总结

备忘录模式是一把状态管理的利剑,通过将对象状态封装到备忘录中,实现了安全、高效的状态保存与恢复。它的核心价值在于在不破坏封装性的前提下,提供灵活的状态管理机制,特别适合撤销、重做或会话管理等场景。在 Spring WebFlux 中,WebSession 的设计体现了备忘录模式的精髓,通过解耦状态存储与管理,助力高并发 Web 应用的开发。与命令模式相比,备忘录模式更专注于状态快照,简单而纯粹。开发者应善用其思想,在需要历史记录或状态恢复的场景中,打造优雅且用户友好的系统。备忘录模式如同一台时光机,让代码在过去与现在间自由穿梭,赋予系统无限可能。

(对您有帮助 && 觉得我总结的还行) -> 受累点个免费的赞👍,谢谢