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

相关推荐
Java面试题总结5 小时前
GatewayWorker 从零到一完整指南
java
脉动数据行情5 小时前
Java OkHttp 完整封装 GC COMEX 美黄金实时盘口定时拉取工具类
java·okhttp·comex 黄金·外盘贵金属
敖行客 Allthinker5 小时前
IM 融合专题:后端架构师的核心修炼
java·开发语言·数据库
HAPPY酷5 小时前
【ROS2】16G 内存笔记本跑 ROS2 仿真?VMware 虚拟机“保姆级”配置指南 (R9 7940HX + RTX 4060)
java·linux·ide·windows·pycharm
折哥的程序人生 · 物流技术专研13 小时前
第4篇:Lambda 简化策略模式(Java 8+)
java·设计模式·策略模式·函数式编程·lambda·代码简化·扩充系列
researcher-Jiang14 小时前
高性能计算之OpenMP——超算习堂学习1
android·java·学习
西门吹-禅16 小时前
java springboot N+1问题
java·开发语言·spring boot
DLYSB_16 小时前
生命通道:如何用 HIS 医疗系统直连网络声光终端,打造“零延误”的危急值响应网关?
java·网络·数据库·报警灯
weixin_BYSJ198716 小时前
SpringBoot + MySQL 乒乓球运动员信息管理系统项目实战--附源码04954
java·javascript·spring boot·python·django·flask·php
AI小码16 小时前
LLM 应用的缓存工程:当每次 API 调用都在燃烧成本
java·人工智能·spring·计算机·llm·编程·api