设计模式之美学习-行为型-状态模式(三十)
什么是状态模式
状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。不过,状态机的实现方式有多种,除了状态模式,比较常用的还有分支逻辑法和查表法
简单来说 状态模式是一种行为设计模式,允许对象在其内部状态改变时改变其行为
状态模式的优点
-
解耦状态和行为:将状态转换逻辑分散到各个状态类中,避免大量的
if-else
或switch-case
语句。 -
易于扩展:新增状态时只需添加新的状态类,无需修改现有代码。
-
符合开闭原则:对扩展开放,对修改关闭。
快速理解状态模式
状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态改变时改变其行为,看起来就像是对象的类发生了改变。状态模式将状态封装成独立的类,并将对状态的行为委托给当前状态对象。
状态模式使用场景
当一个对象的行为取决于它的状态,并且在运行时需要根据状态改变行为时,可以考虑使用状态模式。
当一个对象有多个状态,并且状态之间存在复杂的转换和流转逻辑时,可以使用状态模式来简化代码和提高可维护性。
当需要避免使用大量的条件语句来控制对象的行为时,状态模式可以提供一种更加优雅的解决方案。
状态定义
Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象,在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
State(抽象状态类):定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
什么是有限状态机
解释1:有限状态机,英文翻译是 Finite State Machine,缩写为 FSM,简称为状态机。状态机有 3 个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作
解释2:“超级马里奥”游戏不知道你玩过没有?在游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。在不同的游戏情节下,各个形态会互相转化,并相应的增减积分。比如,初始形态是小马里奥,吃了蘑菇之后就会变成超级马里奥,并且增加 100 积分。
实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)
代码实现(分支逻辑法)
通过if swatch控制状态的处理
public enum State { SMALL(0),//小马里奥 SUPER(1),//超级马里奥 FIRE(2),//获得火焰 CAPE(3);//获得斗篷 private int value; private State(int value) { this.value = value; } public int getValue() { return this.value; } } public class MarioStateMachine { private int score; private State currentState; public MarioStateMachine() { this.score = 0;//默认0积分 this.currentState = State.SMALL;//小马里奥 } /** * 吃了蘑菇 */ public void obtainMushRoom() { //如果是小马里奥积分+100变身超级马里奥 if (currentState.equals(State.SMALL)) { this.currentState = State.SUPER; this.score += 100; } } /** * 获得斗篷 */ public void obtainCape() { //如果是小马里奥或者超级马里奥 状态改为获得斗篷 积分+200 if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER)) { this.currentState = State.CAPE; this.score += 200; } } /** * 获得火焰 */ public void obtainFireFlower() { //如果是小马里奥或者超级马里奥 积分+300 if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER)) { this.currentState = State.FIRE; this.score += 300; } } /** * 遭遇怪物 */ public void meetMonster() { //超级马里奥 遇到怪物 变身小玛丽要积分-200 if (currentState.equals(State.SUPER)) { this.currentState = State.SMALL; this.score -= 100; return; } //获得斗篷状态 积分-200 变身小玛丽要 if (currentState.equals(State.CAPE)) { this.currentState = State.SMALL; this.score -= 200; return; } //获得火焰状态 积分-300 变身小玛丽熬 if (currentState.equals(State.FIRE)) { this.currentState = State.SMALL; this.score -= 300; return; } } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState; } } public class ApplicationDemo { public static void main(String[] args) { MarioStateMachine mario = new MarioStateMachine(); //获得蘑菇 mario.obtainMushRoom(); //获取积分和状态 int score = mario.getScore(); State state = mario.getCurrentState(); System.out.println("mario score: " + score + "; state: " + state); } }
使用状态模式实现
将不同的状态不同的处理逻辑封装成了一个一个状态,避免大量的分支判断状态 做对应的逻辑,将具体实现转移到了各个状态类
//状态接口 定义了不同事件 public interface IMario { //所有状态类的接口 State getName(); //以下是定义的事件 void obtainMushRoom(); void obtainCape(); void obtainFireFlower(); void meetMonster(); } //小马里奥状态 public class SmallMario implements IMario { private MarioStateMachine stateMachine; public SmallMario(MarioStateMachine stateMachine) { this.stateMachine = stateMachine; } @Override public State getName() { return State.SMALL; } /** * 小马里奥获得蘑菇 */ @Override public void obtainMushRoom() { //改变状态机的状态 stateMachine.setCurrentState(new SuperMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 100); } /** * 小马里奥获得斗篷 */ @Override public void obtainCape() { //改变状态机的状态 stateMachine.setCurrentState(new CapeMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 200); } /** * 小马里奥获得火焰 */ @Override public void obtainFireFlower() { //改变状态机的状态 stateMachine.setCurrentState(new FireMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 300); } /** * 小马里奥遇到怪物 */ @Override public void meetMonster() { // do nothing... } } /** * 超级马里奥状态 */ public class SuperMario implements IMario { private MarioStateMachine stateMachine; public SuperMario(MarioStateMachine stateMachine) { this.stateMachine = stateMachine; } @Override public State getName() { return State.SUPER; } /** * 超级马里奥遇到蘑菇 * 什么都不做 */ @Override public void obtainMushRoom() { // do nothing... } /** * 超级马里奥获得斗篷 */ @Override public void obtainCape() { //改变状态机的状态 stateMachine.setCurrentState(new CapeMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 200); } /** * 超级马里奥获得火焰 */ @Override public void obtainFireFlower() { //改变状态机的状态 stateMachine.setCurrentState(new FireMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() + 300); } /** * 超级马里奥遇到怪物 */ @Override public void meetMonster() { //改变状态机的状态 stateMachine.setCurrentState(new SmallMario(stateMachine)); stateMachine.setScore(stateMachine.getScore() - 100); } } // 省略CapeMario、FireMario类... public class MarioStateMachine { private int score; private IMario currentState; // 不再使用枚举来表示状态 public MarioStateMachine() { this.score = 0; this.currentState = new SmallMario(this); } public void obtainMushRoom() { this.currentState.obtainMushRoom(); } public void obtainCape() { this.currentState.obtainCape(); } public void obtainFireFlower() { this.currentState.obtainFireFlower(); } public void meetMonster() { this.currentState.meetMonster(); } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState.getName(); } public void setScore(int score) { this.score = score; } public void setCurrentState(IMario currentState) { this.currentState = currentState; } }
单例模式重构
public interface IMario { State getName(); void obtainMushRoom(MarioStateMachine stateMachine); void obtainCape(MarioStateMachine stateMachine); void obtainFireFlower(MarioStateMachine stateMachine); void meetMonster(MarioStateMachine stateMachine); } public class SmallMario implements IMario { //单例模式 private static final SmallMario instance = new SmallMario(); //私有化构造函数 private SmallMario() {} public static SmallMario getInstance() { return instance; } @Override public State getName() { return State.SMALL; } /** * 动态传入状态机 * @param stateMachine */ @Override public void obtainMushRoom(MarioStateMachine stateMachine) { stateMachine.setCurrentState(SuperMario.getInstance()); stateMachine.setScore(stateMachine.getScore() + 100); } /** * 动态传入状态机 * @param stateMachine */ @Override public void obtainCape(MarioStateMachine stateMachine) { stateMachine.setCurrentState(CapeMario.getInstance()); stateMachine.setScore(stateMachine.getScore() + 200); } /** * 动态传入状态机 * @param stateMachine */ @Override public void obtainFireFlower(MarioStateMachine stateMachine) { stateMachine.setCurrentState(FireMario.getInstance()); stateMachine.setScore(stateMachine.getScore() + 300); } /** * 动态传入状态机 * @param stateMachine */ @Override public void meetMonster(MarioStateMachine stateMachine) { // do nothing... } } // 省略SuperMario、CapeMario、FireMario类... public class MarioStateMachine { private int score; private IMario currentState; public MarioStateMachine() { this.score = 0; this.currentState = SmallMario.getInstance(); } public void obtainMushRoom() { //传入当前对象 到状态 this.currentState.obtainMushRoom(this); } public void obtainCape() { //传入当前对象 到状态 this.currentState.obtainCape(this); } public void obtainFireFlower() { //传入当前对象 到状态 this.currentState.obtainFireFlower(this); } public void meetMonster() { //传入当前对象 到状态 this.currentState.meetMonster(this); } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState.getName(); } public void setScore(int score) { this.score = score; } public void setCurrentState(IMario currentState) { this.currentState = currentState; } }
实际场景
订单状态、审核状态场景
经常遇到订单状态,定义了状态机的流程,当状态变更大量的if 状态能否下一步状态,将状态的变更内聚到各个状态。
审核状态单据状态状态机
状态的定义
public enum AwardPunishmentAuditStatusEnum implements INumberValue<Short>, IDescValue { WAIT_AUDIT((short) 10, "待审核"), AUDIT_PASS((short) 20, "审核通过"), AUDIT_FAIL((short) 30, "审核不通过"), CANCEL((short) 40, "已作废"), ; private final Short value; private final String desc; AwardPunishmentAuditStatusEnum(Short value, String desc) { this.value = value; this.desc = desc; } }
AwardPunishmentAuditStatusFactory
/** * @Author liqiang * @Description 奖惩单审核状态转换工厂 * @Date 2024/11/13/15:19 */ public class AwardPunishmentAuditStatusFactory { public AwardPunishmentAuditStatus create( AwardPunishmentAggregate aggregate) { AwardPunishmentAuditStatus awardPunishmentAuditStatus = null; switch (aggregate.getAuditStatus()) { case WAIT_AUDIT: awardPunishmentAuditStatus = new WaitAuditStatus(aggregate); break; case AUDIT_PASS: awardPunishmentAuditStatus = new AuditPassStatus(aggregate); break; case AUDIT_FAIL: awardPunishmentAuditStatus = new AuditFailStatus(aggregate); break; case CANCEL: awardPunishmentAuditStatus = new CancelStatus(aggregate); break; default: throw new YxtRuntimeException(ResponseCodeType.PARA_ERROR, "状态异常"); } return awardPunishmentAuditStatus; } }
抽象状态实现(抽象状态类)
/** * @Author liqiang * @Description 奖惩单状态抽象 * @Date 2024/11/13/15:04 */ public interface AwardPunishmentAuditStatus { /** * 当前状态 * * @return */ AwardPunishmentAuditStatusEnum state(); /** * 更改状态为审核通过 */ default void changeAuditPass() { if (state() == AwardPunishmentAuditStatusEnum.AUDIT_PASS) { throw new YxtRuntimeException(ResponseCodeType.PARA_ERROR, AwardPunishmentResponseCodeType.DUPLICATE); } throw new YxtRuntimeException(ResponseCodeType.PARA_ERROR, String.format("当前状态不能切换为[%s]状态", state().value())); } /** * 更改状态为审核不通过 */ default void changeAuditFail() { if (state() == AwardPunishmentAuditStatusEnum.AUDIT_FAIL) { throw new YxtRuntimeException(ResponseCodeType.PARA_ERROR, AwardPunishmentResponseCodeType.DUPLICATE); } throw new YxtRuntimeException(ResponseCodeType.PARA_ERROR, String.format("当前状态不能切换为[%s]状态", state().value())); } /** * 更改状态为作废 */ default void changeCancel() { if (state() == AwardPunishmentAuditStatusEnum.CANCEL) { throw new YxtRuntimeException(ResponseCodeType.PARA_ERROR, AwardPunishmentResponseCodeType.DUPLICATE); } throw new YxtRuntimeException(ResponseCodeType.PARA_ERROR, String.format("当前状态不能切换为[%s]状态", state().value())); } }
审核不通过状态(具体状态类)
没有任何实现,因为是终态
/** * @Author liqiang * @Description 审核不通过状态 * @Date 2024/11/13/15:14 */ public class AuditFailStatus implements AwardPunishmentAuditStatus { private final AwardPunishmentAggregate aggregate; public AuditFailStatus(AwardPunishmentAggregate aggregate) { this.aggregate = aggregate; } @Override public AwardPunishmentAuditStatusEnum state() { return AwardPunishmentAuditStatusEnum.AUDIT_FAIL; } }
审核通过状态(具体状态类)
仅能变更为作废和修改为审核不通过,同时如果单据冻结和已经推送ERP不能操作
/** * @Author liqiang * @Description 审核通过状态 * @Date 2024/11/13/15:12 */ public class AuditPassStatus implements AwardPunishmentAuditStatus { private final AwardPunishmentAggregate aggregate; public AuditPassStatus(AwardPunishmentAggregate aggregate) { this.aggregate = aggregate; } @Override public AwardPunishmentAuditStatusEnum state() { return AwardPunishmentAuditStatusEnum.AUDIT_PASS; } @Override public void changeAuditFail() { Conditions.assertFalse(aggregate.getAwardPunishmentStatus().equals(AwardPunishmentStatusEnum.FROZEN), "单据冻结中"); Conditions.assertTrue(aggregate.canProcessOrder(), ResponseCodeType.BIZ_EXCEPTION, "单据已经推送ERP不能操作", AwardPunishmentResponseCodeType.ALREADY_PUSH_ERP.getCode(), AwardPunishmentResponseCodeType.ALREADY_PUSH_ERP.getDesc()); this.aggregate.setAuditStatus(AwardPunishmentAuditStatusEnum.AUDIT_FAIL); } @Override public void changeCancel() { Conditions.assertTrue(aggregate.canProcessOrder(), ResponseCodeType.BIZ_EXCEPTION, "单据已经推送ERP不能操作", AwardPunishmentResponseCodeType.ALREADY_PUSH_ERP.getCode(), AwardPunishmentResponseCodeType.ALREADY_PUSH_ERP.getDesc()); this.aggregate.setAuditStatus(AwardPunishmentAuditStatusEnum.CANCEL); } }
作废状态(具体状态类)
也是终态 没有任何实现
/** * @Author liqiang * @Description 已作废状态 * @Date 2024/11/13/15:15 */ public class CancelStatus implements AwardPunishmentAuditStatus { private final AwardPunishmentAggregate aggregate; @Override public AwardPunishmentAuditStatusEnum state() { return AwardPunishmentAuditStatusEnum.CANCEL; } public CancelStatus(AwardPunishmentAggregate aggregate) { this.aggregate = aggregate; } }
待审核状态(具体状态类)
待审核可以变更为审核通过、审核不通过、和作废
/** * @Author liqiang * @Description 待审核状态 * @Date 2024/11/13/15:05 */ public class WaitAuditStatus implements AwardPunishmentAuditStatus { private final AwardPunishmentAggregate aggregate; public WaitAuditStatus(AwardPunishmentAggregate aggregate) { this.aggregate = aggregate; } @Override public AwardPunishmentAuditStatusEnum state() { return AwardPunishmentAuditStatusEnum.WAIT_AUDIT; } @Override public void changeAuditPass() { Conditions.assertFalse(aggregate.getAwardPunishmentStatus().equals(AwardPunishmentStatusEnum.FROZEN), "单据冻结中"); aggregate.setAuditStatus(AwardPunishmentAuditStatusEnum.AUDIT_PASS); } public void changeAuditFail() { Conditions.assertFalse(aggregate.getAwardPunishmentStatus().equals(AwardPunishmentStatusEnum.FROZEN), "单据冻结中"); aggregate.setAuditStatus(AwardPunishmentAuditStatusEnum.AUDIT_FAIL); } public void changeCancel() { Conditions.assertFalse(aggregate.getAwardPunishmentStatus().equals(AwardPunishmentStatusEnum.FROZEN), "单据冻结中"); aggregate.setAuditStatus(AwardPunishmentAuditStatusEnum.CANCEL); } }
当要变更状态的时候如何使用Context(环境类)
/** * 奖惩聚合 * * @author liqiang * @date 2024/10/24 11:30 **/ @Slf4j @Getter @Setter @NoArgsConstructor @ToString(callSuper = true) public class AwardPunishmentAggregate extends BaseAggregateRoot<BaseAwardPunishmentDomainEvent<?>> { private Long id; /** * 奖惩单no */ private String awardPunishmentNo; /** * 奖惩单状态(10:正常,20:冻结,30:作废) */ private AwardPunishmentStatusEnum awardPunishmentStatus; /* * 审核奖惩单 */ public void auditPunishment(AwardPunishmentAuditCommand command, OperateContext operateContext) { OperateContextUtil.checkOperateContextValid(operateContext); //审核状态 AwardPunishmentAuditStatusFactory awardPunishmentAuditStatusFactory = new AwardPunishmentAuditStatusFactory(); AwardPunishmentAuditStatus awardPunishmentAuditStatus = awardPunishmentAuditStatusFactory.create(this); AwardPunishmentLogOperateTypeEnum operateTypeEnum = null; switch (command.getAuditStatus()) { case AUDIT_FAIL://审核不通过 awardPunishmentAuditStatus.changeAuditFail(); operateTypeEnum = AwardPunishmentLogOperateTypeEnum.AUDIT_FAIL; break; case AUDIT_PASS://审核通过 awardPunishmentAuditStatus.changeAuditPass(); operateTypeEnum = AwardPunishmentLogOperateTypeEnum.AUDIT_PASS; break; case CANCEL://作废 this.cancelPkId = this.id; awardPunishmentAuditStatus.changeCancel(); operateTypeEnum = AwardPunishmentLogOperateTypeEnum.AUDIT_REJECT; break; default: throw new YxtRuntimeException(ResponseCodeType.PARA_ERROR, "不支持的审核类型"); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
2019-04-15 MySql优化- join匹配原理(一)