状态模式实战:Row 对象状态机
本文从真实 ORM 框架场景出发,实现一套自动追踪数据状态、自动驱动 INSERT/UPDATE/DELETE的 Row 状态机,代码简洁、鲁棒性强、可直接落地生产,兼顾设计模式思想与工程最佳实践。
文章目录
- [状态模式实战:Row 对象状态机](#状态模式实战:Row 对象状态机)
-
- 一、场景与目标
- 二、状态定义(枚举化,无魔法值)
- 三、状态流转规则(标准状态机语义)
- [四、Row 完整实现(生产级)](#四、Row 完整实现(生产级))
- [五、BaseDao 状态驱动持久化](#五、BaseDao 状态驱动持久化)
- 六、测试用例(可直接运行)
- 七、设计模式本质
-
- [1. 标准状态模式](#1. 标准状态模式)
- [2. 本文轻量状态机(最优实践)](#2. 本文轻量状态机(最优实践))
- 八、亮点总结
- 九、适用场景
- 十、扩展方向(生产进阶)
- 结语
一、场景与目标
在轻量级 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 框架
- 表单数据自动保存
- 表格编辑、行状态追踪
- 数据审计日志
- 低代码平台数据层
十、扩展方向(生产进阶)
- 实现 Serializable 支持分布式
- 增加字段校验、类型安全
- 批量赋值、批量状态重置
- 字段级回滚(基于 oldDataMap)
- 状态变更监听器 / 日志埋点
- 与 Spring/JDBC/MyBatis 集成
结语
这套 Row 状态机是设计模式落地的典型案例 :不盲目套用教科书,而是根据场景做极简工程化实现,既保证模式思想,又保证性能、可维护性与落地效率。