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

相关推荐
城管不管17 分钟前
ReAct、Plan-and-Execute、Reflection 三大智能 Agent 范式核心区别
java·人工智能·算法·spring·ai·动态规划
IT小白杨27 分钟前
从环境制备到自动化工作流:多账号运营的工程化架构拆解
java·经验分享·自动化·安全架构·指纹浏览器
豆瓣鸡1 小时前
算法日记 - Day3
java·开发语言·算法
萧瑟余晖1 小时前
Java深入解析篇九之NIO详解
java·网络·nio
The Chosen One9851 小时前
高进度算法模板速记(待完善)
java·前端·算法
玛艾露贝2 小时前
Browser-Use在UI自动化测试中的应用
ui
brave_zhao2 小时前
Axure RP 9模板的使用
ui·axure·photoshop
极光代码工作室4 小时前
基于SpringBoot的课程预约系统
java·springboot·web开发·后端开发
骊城英雄4 小时前
基于C#+avalonia ui实现的跨平台点胶机灌胶监控控制上位机软件
开发语言·ui·c#
Leighteen4 小时前
`try-finally` 里的 `return`:为什么 `finally` 会悄悄改掉返回值、吞掉异常
java·开发语言