Java设计模式实战:备忘录模式与状态机模式的“状态管理”双雄

发布于:2025-07-13 ⋅ 阅读:(23) ⋅ 点赞:(0)

引言

在软件开发中,“状态管理”是永恒的主题:从文本编辑器的“撤销/重做”到电商订单的“待支付→已发货→已完成”流转,从游戏角色的“满血→受伤→濒死”状态切换到数据库事务的“提交/回滚”,如何优雅地处理状态的保存、恢复与转换,直接影响代码的可维护性和扩展性。

本文将深入解析两种与状态管理强相关的设计模式——备忘录模式(Memento Pattern)状态机模式(State Machine Pattern),通过文本编辑器撤销功能、电商订单状态流转两大实战案例,拆解它们的设计逻辑,并对比两者的适用边界,助你在实际开发中“选对模式,管好状态”。


一、备忘录模式:给状态“拍快照”的时光机

1.1 定义与核心角色

备忘录模式的官方定义是:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便后续恢复对象到该状态

它的核心由三个角色组成(图1):

  • Originator(原发器):需要保存状态的对象(如文本编辑器),负责创建/恢复备忘录;
  • Memento(备忘录):存储Originator的状态,通常只允许Originator访问内部细节;
  • Caretaker(管理者):管理备忘录的“保管员”,负责保存、获取备忘录,但不修改其内容。

备忘录模式类图
图1:备忘录模式类图

1.2 实战案例:文本编辑器的撤销功能

假设我们要开发一个支持“撤销”的文本编辑器,用户输入内容后,可通过“Ctrl+Z”回退到上一步状态。用备忘录模式实现的关键是:每次输入后保存当前状态,撤销时恢复最近一次的状态。

1.2.1 定义Memento(备忘录)

备忘录需要保存Originator的关键状态(这里是文本内容),且仅允许Originator访问。通过私有构造+接口限制实现封装:

// 备忘录类(仅允许Originator访问内部状态)
public class TextMemento {
    private final String content;  // 保存的文本内容

    // 构造方法私有,仅允许Originator创建
    TextMemento(String content) {
        this.content = content;
    }

    // 提供给Originator的获取状态方法
    String getContent() {
        return content;
    }
}
1.2.2 定义Originator(文本编辑器)

文本编辑器(Originator)负责创建备忘录(createMemento())和恢复状态(restoreMemento()):

public class TextEditor {
    private String content = "";  // 当前文本内容

    // 创建备忘录(保存当前状态)
    public TextMemento createMemento() {
        return new TextMemento(content);
    }

    // 恢复备忘录(回退到保存状态)
    public void restoreMemento(TextMemento memento) {
        this.content = memento.getContent();
    }

    // 模拟用户输入(修改状态)
    public void appendText(String text) {
        this.content += text;
    }

    // 获取当前内容(用于展示)
    public String getContent() {
        return content;
    }
}
1.2.3 定义Caretaker(历史记录管理器)

历史记录管理器(Caretaker)用栈保存备忘录,实现“后进先出”的撤销顺序:

import java.util.Stack;

public class HistoryManager {
    private final Stack<TextMemento> mementoStack = new Stack<>();  // 用栈保存历史状态

    // 保存新状态(用户输入后调用)
    public void saveState(TextMemento memento) {
        mementoStack.push(memento);
    }

    // 撤销到上一状态(用户触发Ctrl+Z时调用)
    public TextMemento undo() {
        if (mementoStack.isEmpty()) {
            return null;  // 无历史记录可撤销
        }
        return mementoStack.pop();  // 弹出最近一次保存的状态
    }
}
1.2.4 测试流程
public class MementoDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        HistoryManager history = new HistoryManager();

        // 用户输入"Hello ",保存状态
        editor.appendText("Hello ");
        history.saveState(editor.createMemento());
        System.out.println("当前内容: " + editor.getContent());  // 输出: Hello 

        // 用户输入"World!",保存状态
        editor.appendText("World!");
        history.saveState(editor.createMemento());
        System.out.println("当前内容: " + editor.getContent());  // 输出: Hello World!

        // 用户触发撤销(Ctrl+Z)
        TextMemento undoState = history.undo();
        if (undoState != null) {
            editor.restoreMemento(undoState);
        }
        System.out.println("撤销后内容: " + editor.getContent());  // 输出: Hello 
    }
}

1.3 备忘录模式的优缺点与适用场景

优点 缺点 适用场景
状态保存与恢复解耦 可能占用较多内存(保存大量状态) 需撤销/重做的功能(编辑器、游戏存档)
封装性好(状态由Originator管理) 频繁保存可能影响性能 事务回滚(数据库、分布式事务)

二、状态机模式:让状态转换“自动机”化

2.1 定义与核心角色

状态机模式(通常指状态模式,State Pattern)的定义是:允许对象在其内部状态改变时改变其行为,对象看起来好像修改了其类。其核心思想是将状态相关的行为封装到独立的状态类中,通过状态切换触发不同行为。

它的核心由三个角色组成(图2):

  • Context(上下文):持有当前状态的引用,将行为委托给当前状态(如订单对象);
  • State(状态接口):定义所有状态的公共行为(如订单的支付、发货操作);
  • ConcreteState(具体状态):实现State接口,处理当前状态下的具体行为,并负责状态转换。

状态模式类图
图2:状态模式类图

2.2 实战案例:电商订单状态流转

电商订单通常有“待支付→已支付→已发货→已完成”的状态流转,不同状态下允许的操作不同(如“待支付”状态可取消,“已发货”状态不可取消)。用状态机模式实现的关键是:将每个状态的行为封装到独立类中,状态切换时自动委托行为。

2.2.1 定义State(订单状态接口)
public interface OrderState {
    // 支付操作(待支付→已支付)
    void pay(OrderContext context);

    // 发货操作(已支付→已发货)
    void deliver(OrderContext context);

    // 确认收货(已发货→已完成)
    void confirm(OrderContext context);

    // 取消订单(仅部分状态允许)
    void cancel(OrderContext context);
}
2.2.2 定义ConcreteState(具体状态类)

以“待支付状态(PendingPaymentState)”为例,它允许支付和取消,但不允许发货或确认:

public class PendingPaymentState implements OrderState {
    @Override
    public void pay(OrderContext context) {
        System.out.println("支付成功,订单状态更新为[已支付]");
        context.setState(new PaidState());  // 支付后切换到已支付状态
    }

    @Override
    public void deliver(OrderContext context) {
        throw new IllegalStateException("待支付状态不可发货");
    }

    @Override
    public void confirm(OrderContext context) {
        throw new IllegalStateException("待支付状态不可确认收货");
    }

    @Override
    public void cancel(OrderContext context) {
        System.out.println("取消成功,订单状态更新为[已取消]");
        context.setState(new CancelledState());  // 取消后切换到已取消状态
    }
}

其他状态类(PaidState已支付、DeliveredState已发货、CompletedState已完成、CancelledState已取消)类似,仅实现当前状态允许的操作。

2.2.3 定义Context(订单上下文)

订单上下文(OrderContext)持有当前状态,并提供状态切换的入口:

public class OrderContext {
    private OrderState currentState;  // 当前状态

    public OrderContext() {
        this.currentState = new PendingPaymentState();  // 初始状态为待支付
    }

    // 设置新状态(由具体状态类调用)
    public void setState(OrderState state) {
        this.currentState = state;
    }

    // 暴露给外部的操作入口(委托给当前状态)
    public void pay() {
        currentState.pay(this);
    }

    public void deliver() {
        currentState.deliver(this);
    }

    public void confirm() {
        currentState.confirm(this);
    }

    public void cancel() {
        currentState.cancel(this);
    }
}
2.2.4 测试流程
public class StateMachineDemo {
    public static void main(String[] args) {
        OrderContext order = new OrderContext();

        // 初始状态:待支付
        order.pay();       // 输出: 支付成功,订单状态更新为[已支付]
        order.deliver();   // 输出: 发货成功,订单状态更新为[已发货]
        order.confirm();   // 输出: 确认收货成功,订单状态更新为[已完成]
        order.cancel();    // 抛出异常: 已完成状态不可取消
    }
}

2.3 状态机模式的优缺点与适用场景

优点 缺点 适用场景
状态转换逻辑清晰(每个状态独立) 状态类数量可能膨胀(状态多时代码量增加) 状态流转复杂的场景(订单、工作流)
符合开闭原则(新增状态只需添加新类) 状态切换需谨慎设计(避免循环依赖) 设备状态管理(空调、电梯)

三、备忘录模式 vs 状态机模式:状态管理的“左右互搏”

3.1 核心目标不同

  • 备忘录模式:聚焦“状态的保存与恢复”,解决“如何回退到历史状态”的问题(如撤销、回滚);
  • 状态机模式:聚焦“状态的转换与行为”,解决“不同状态下允许哪些操作”的问题(如订单流转、权限控制)。

3.2 状态的生命周期不同

  • 备忘录模式的状态是“静态的”:保存的是某个时间点的快照,恢复时直接覆盖当前状态;
  • 状态机模式的状态是“动态的”:状态之间有严格的转换规则,每个状态定义了允许的操作。

3.3 协作方式不同

  • 备忘录模式依赖Caretaker管理多个历史状态,Originator通过Memento与Caretaker交互;
  • 状态机模式依赖Context委托行为给当前State,State之间通过修改Context的状态引用来切换。

3.4 典型组合使用场景

两者并非互斥,而是可以互补。例如,在订单系统中:

  • 状态机模式管理“待支付→已支付→已发货”的正常流转;
  • 备忘录模式保存每个状态变更前的快照,支持“撤销状态转换”(如用户误操作发货后,可撤销回“已支付”状态)。

四、总结:选对模式,管好状态

  • 选备忘录模式:当需要“保存历史状态,支持撤销/恢复”时(如编辑器、游戏存档、事务回滚);
  • 选状态机模式:当需要“定义状态转换规则,不同状态有不同行为”时(如订单流转、设备状态、工作流引擎);
  • 组合使用:复杂系统中,两者可结合实现“状态流转+历史回溯”的双重能力(如电商订单的“修改地址→撤销修改”)。

状态管理是软件设计的“地基”,备忘录模式和状态机模式分别提供了“保存历史”和“规范流转”的解决方案。理解它们的设计思想和适用边界,能让你的代码在“状态管理”这个关键领域,既灵活又健壮。


网站公告

今日签到

点亮在社区的每一天
去签到