设计模式之美学习-行为型-状态模式(三十)

什么是状态模式

状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。不过,状态机的实现方式有多种,除了状态模式,比较常用的还有分支逻辑法和查表法

简单来说 状态模式是一种行为设计模式,允许对象在其内部状态改变时改变其行为

状态模式的优点

  • 解耦状态和行为:将状态转换逻辑分散到各个状态类中,避免大量的 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, "不支持的审核类型");
        }
    }
}
复制代码

 

posted @   意犹未尽  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
历史上的今天:
2019-04-15 MySql优化- join匹配原理(一)
点击右上角即可分享
微信分享提示