状态模式实战——Row对象的状态机

状态模式实战:Row 对象状态机

本文从真实 ORM 框架场景出发,实现一套自动追踪数据状态、自动驱动 INSERT/UPDATE/DELETE的 Row 状态机,代码简洁、鲁棒性强、可直接落地生产,兼顾设计模式思想与工程最佳实践。

文章目录


一、场景与目标

在轻量级 ORM / 数据层框架中,表格行 Row 需要具备:

  • 自动识别:新增 / 修改 / 未修改 / 删除
  • 自动执行:INSERT / UPDATE / DELETE 无需手动判断
  • 旧值备份:支持审计追溯、增量更新
  • 状态安全:禁止非法状态切换、异常防护

最终实现:调用方只管设值,状态机全权负责行为


二、状态定义(枚举化,无魔法值)

java 复制代码
/**
 * Row 数据行状态枚举
 */
public enum RowState {
    /** 未修改,无操作 */
    UNMODIFIED(0, "未修改"),
    /** 新增,执行 INSERT */
    INSERT(1, "新增"),
    /** 修改,执行 UPDATE */
    UPDATE(3, "修改"),
    /** 删除,执行 DELETE */
    DELETE(4, "删除");

    private final int code;
    private final String desc;

    RowState(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

三、状态流转规则(标准状态机语义)

#mermaid-svg-jfiNOz9oWnmNC0Y7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .error-icon{fill:#552222;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .marker.cross{stroke:#333333;}#mermaid-svg-jfiNOz9oWnmNC0Y7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 p{margin:0;}#mermaid-svg-jfiNOz9oWnmNC0Y7 defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-jfiNOz9oWnmNC0Y7 g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-jfiNOz9oWnmNC0Y7 g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-jfiNOz9oWnmNC0Y7 g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jfiNOz9oWnmNC0Y7 .edgeLabel .label text{fill:#333;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .label div .edgeLabel{color:#333;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 #statediagram-barbEnd{fill:#333333;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .cluster-label,#mermaid-svg-jfiNOz9oWnmNC0Y7 .nodeLabel{color:#131300;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .note-edge{stroke-dasharray:5;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-note text{fill:black;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram-note .nodeLabel{color:black;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagram .edgeLabel{color:red;}#mermaid-svg-jfiNOz9oWnmNC0Y7 #dependencyStart,#mermaid-svg-jfiNOz9oWnmNC0Y7 #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-jfiNOz9oWnmNC0Y7 .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jfiNOz9oWnmNC0Y7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 初始加载
设置新字段
修改已有字段
继续修改
继续修改
标记删除
标记删除
标记删除
已删除,禁止修改
UNMODIFIED
INSERT
UPDATE
DELETE

当前状态 操作 目标状态 行为
UNMODIFIED 新字段赋值 INSERT 标记为新增
UNMODIFIED 旧字段修改 UPDATE 备份旧值
INSERT 任意赋值 INSERT 保持新增
UPDATE 任意赋值 UPDATE 保持修改
任意 标记删除 DELETE 禁止再修改

四、Row 完整实现(生产级)

java 复制代码
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 数据行对象:轻量级状态机实现
 */
public class Row {
    // 当前状态
    private RowState state = RowState.UNMODIFIED;

    // 当前数据
    private final Map<String, Object> dataMap = new HashMap<>();

    // 旧值备份(审计/增量更新)
    private final Map<String, Object> oldDataMap = new HashMap<>();

    // ==================== 状态判断 ====================
    public boolean isUnmodified() {
        return RowState.UNMODIFIED.equals(state);
    }

    public boolean isInsert() {
        return RowState.INSERT.equals(state);
    }

    public boolean isUpdate() {
        return RowState.UPDATE.equals(state);
    }

    public boolean isDelete() {
        return RowState.DELETE.equals(state);
    }

    // ==================== 核心:状态驱动赋值 ====================
    public void setItemValue(Object key, Object value) {
        Objects.requireNonNull(key, "key cannot be null");
        String field = key.toString();
        Object oldVal = dataMap.get(field);

        // 删除状态禁止修改
        if (isDelete()) {
            throw new IllegalStateException("已删除行不可修改");
        }

        // 未修改状态才需要切换状态
        if (isUnmodified()) {
            if (oldVal == null) {
                state = RowState.INSERT;
            } else {
                state = RowState.UPDATE;
                oldDataMap.putIfAbsent(field, oldVal);
            }
        }

        // 写入新值
        dataMap.put(field, value);
    }

    // ==================== 取值 ====================
    public Object getItemValue(Object key) {
        return key == null ? null : dataMap.get(key.toString());
    }

    public String getItemStringValue(Object key) {
        Object v = getItemValue(key);
        return v == null ? "" : v.toString();
    }

    // ==================== 状态操作 ====================
    public void markDelete() {
        this.state = RowState.DELETE;
    }

    public void reset() {
        this.state = RowState.UNMODIFIED;
        this.oldDataMap.clear();
    }

    // ==================== Getter ====================
    public RowState getState() {
        return state;
    }

    public Map<String, Object> getDataMap() {
        return new HashMap<>(dataMap);
    }

    public Map<String, Object> getOldDataMap() {
        return new HashMap<>(oldDataMap);
    }
}

五、BaseDao 状态驱动持久化

java 复制代码
/**
 * 基础 DAO:状态自动决定 SQL 执行
 */
public class BaseDao {

    public void save(Row row) {
        if (row.isInsert()) {
            doInsert(row);
        } else if (row.isUpdate()) {
            doUpdate(row);
        } else if (row.isDelete()) {
            doDelete(row);
        }
    }

    protected void doInsert(Row row) {
        System.out.println("INSERT => " + row.getDataMap());
        row.reset();
    }

    protected void doUpdate(Row row) {
        System.out.println("UPDATE => new:" + row.getDataMap() + " old:" + row.getOldDataMap());
        row.reset();
    }

    protected void doDelete(Row row) {
        System.out.println("DELETE => " + row.getDataMap());
        row.reset();
    }
}

六、测试用例(可直接运行)

java 复制代码
public class RowTest {
    public static void main(String[] args) {
        BaseDao dao = new BaseDao();
        Row row = new Row();

        // 新增
        row.setItemValue("id", 1001);
        row.setItemValue("name", "test");
        dao.save(row);

        // 修改
        row.setItemValue("name", "updated");
        dao.save(row);

        // 删除
        row.markDelete();
        dao.save(row);

        // 已删除禁止修改
        try {
            row.setItemValue("name", "fail");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

七、设计模式本质

1. 标准状态模式

  • State 接口 + N 个实现类
  • 完全遵循开闭原则
  • 适合复杂状态机

2. 本文轻量状态机(最优实践)

  • 枚举 + 内部状态判断
  • 无多余类、无过度设计
  • 适合状态少、流转固定的业务(Row 场景)

核心一致:状态决定行为,而非调用方判断。


八、亮点总结

✅ 状态枚举化,无魔法数字、可读性强

✅ 自动状态流转,调用方无感知

✅ 旧值备份,支持审计与增量更新

✅ 删除状态防护,禁止非法修改

✅ 鲁棒性强:空指针防护、状态安全

✅ 极简代码,可直接嵌入 ORM / 数据层

✅ 符合状态模式思想,不过度设计


九、适用场景

  • 轻量级 ORM 框架
  • 表单数据自动保存
  • 表格编辑、行状态追踪
  • 数据审计日志
  • 低代码平台数据层

十、扩展方向(生产进阶)

  1. 实现 Serializable 支持分布式
  2. 增加字段校验、类型安全
  3. 批量赋值、批量状态重置
  4. 字段级回滚(基于 oldDataMap)
  5. 状态变更监听器 / 日志埋点
  6. 与 Spring/JDBC/MyBatis 集成

结语

这套 Row 状态机是设计模式落地的典型案例 :不盲目套用教科书,而是根据场景做极简工程化实现,既保证模式思想,又保证性能、可维护性与落地效率。

相关推荐
葡萄成熟时 !2 小时前
JAVA 常用API学习笔记
java·笔记·学习
Rain的Java大神之路3 小时前
介绍一下分布式事务
java·分布式·后端·spring·spring cloud·架构·springcloud
吴声子夜歌4 小时前
Java面试题——基础(一)
java·开发语言
南城以南溫暖如初1475 小时前
外卖CPS实战指南:从技术选型到运营落地全流程解析
java·spring boot·mysql·架构·vue
傻啦嘿哟5 小时前
英雄联盟皮肤爬虫:爬取全皮肤价格与特效,做比价工具
java·c++·爬虫
码匠许师傅6 小时前
【C++ 面试真题】24. 聊聊 C++ 的时间日期处理
java·c++·面试
mqiqe7 小时前
AgentScope Java 2.0 Agent 状态存储(AgentStateStore)深度解析:构建可恢复、可扩展的智能体运行时
java·运维·网络
zhifou1234567 小时前
java 17升级安装
java·开发语言
用户3721574261357 小时前
如何使用 Java 将 Markdown 转换为 PDF(含自定义设置)
java
用户3721574261358 小时前
如何使用 Java 在 Word 文档中添加和删除水印:分步指南
java