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

相关推荐
zhbi981 小时前
LVGL8.3标签Label高级应用
ui·lvgl
搬石头的马农1 小时前
Claude Code SpringBoot开发:从0到1搭建企业级项目的6个核心Skill
java·人工智能·spring boot·后端·ai编程
西安邮电大学1 小时前
Redis为什么快?
java·redis·后端·其他·面试
折哥的程序人生 · 物流技术专研2 小时前
《Java 100 天进阶之路》第39篇:Java泛型方法的定义和使用
java·开发语言·后端·面试·求职招聘
土狗TuGou2 小时前
SQL内功笔记 · 第6篇:窗口函数的使用ROW_NUMBER等
java·数据库·后端·sql·mysql
Chase_______2 小时前
【Java基础核心知识点全解·09】Java 内存布局与垃圾回收详解:栈、堆、栈帧、GC Roots 与对象回收
java·开发语言
武子康2 小时前
Java-11 深入浅出 MyBatis 一级缓存详解:从原理到失效场景 Executor
java·后端
寻道码路2 小时前
LangChain4j Java AI 应用开发实战(十):Embedding 模型与文本分类 - 语义向量化
java·人工智能·ai·embedding
折哥的程序人生 · 物流技术专研2 小时前
Java 23 种设计模式:从踩坑到精通 | 抽象工厂 —— 支付/收款如何成套创建?跨平台 UI 如何一键换肤?
java·开发语言·后端·设计模式