返回顶部
首页 > 资讯 > 后端开发 > Python >Java实现有限状态机的推荐方案分享
  • 582
分享到

Java实现有限状态机的推荐方案分享

2024-04-02 19:04:59 582人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录一、背景 二、推荐方式2.1 自定义的枚举 2.2 外部枚举 三、总结 一、背景 平时工作开发过程中,难免会用到状态机(状态的流转)。 如奖学金审批流程、请假审批流程、竞标流程

一、背景

平时工作开发过程中,难免会用到状态机(状态的流转)。

如奖学金审批流程、请假审批流程、竞标流程等,都需要根据不同行为转到不同的状态。

下面是一个简单的模拟状态机:

有些同学会选择将状态定义为常量,使用 if else 来流转状态,不太优雅。

有些同学会考虑将状态定义为枚举。

但是定义为枚举之后,大多数同学会选择使用 switch 来流转状态:


import lombok.Getter;

public enum State {

    STATE_A("A"),
    STATE_B("B"),
    STATE_C("C"),
    STATE_D("D");

    @Getter
    private final String value;

    State(String value) {
        this.value = value;
    }

    public static State getByValue(String value) {
        for (State state : State.values()) {
            if (state.getValue().equals(value)) {
                return state;
            }
        }
        return null;
    }

    
    public static State getApprovedState(State currentState) {
        switch (currentState) {
            case STATE_A:
                return STATE_B;
            case STATE_B:
                return STATE_C;
            case STATE_C:
                return STATE_D;
            case STATE_D:
            default:
               throw new IllegalStateException("当前已终态");
        }

    }

    
    public static State getRejectedState(State currentState) {
        switch (currentState) {
            case STATE_A:
                throw new IllegalStateException("当前状态不支持拒绝");
            case STATE_B:
            case STATE_C:
            case STATE_D:
            default:
                return STATE_A;
        }
    }
}

上面这种写法有几个弊端:

(1) getByValue 每次获取枚举值都要循环一次当前枚举的所有常量,时间复杂度是
O(N),虽然耗时非常小,但总有些别扭,作为有追求的程序员,应该尽量想办法优化掉。

(2) 总感觉使用 switch-case 实现状态流转,更多的是面向过程的产物。虽然可以实现功能,但没那么“面向对象”,既然 State 枚举就是用来表示状态,如果同意和拒绝可以通过 State 对象的方法获取就会更直观一些。

二、推荐方式

2.1 自定义的枚举

通常状态流转有两种方向,一种是赞同,一种是拒绝,分别流向不同的状态。

由于本文讨论的是有限状态,我们可以将状态定义为枚举比较契合,除非初态和终态,否则赞同和拒绝都会返回一个状态。

下面只是一个DEMO, 实际编码时可以自由发挥。

该 Demo 的好处是:

1 使用 CACHE缓存,避免每次通过 value 获取 State都循环 State 枚举数组

2 定义【同意】和【拒绝】抽象方法,每个 State 通过实现该方法来流转状态。

3 状态的定义和转换都收拢在一个枚举中,更容易维护

虽然代码看似更多一些,但是更“面向对象”一些。


package basic;

import lombok.Getter;

import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public enum State {
    
    STATE_A("A") {
        @Override
        State getApprovedState() {
            return STATE_B;
        }

        @Override
        State getRejectedState() {
            throw new IllegalStateException("STATE_A 不支持拒绝");
        }
    },
    STATE_B("B") {
        @Override
        State getApprovedState() {
            return STATE_C;
        }

        @Override
        State getRejectedState() {
            return STATE_A;
        }
    },
    STATE_C("C") {
        @Override
        State getApprovedState() {
            return STATE_D;
        }

        @Override
        State getRejectedState() {
            return STATE_A;
        }
    },
    STATE_D("D") {
        @Override
        State getApprovedState() {
             throw new IllegalStateException("当前已终态");
        }

        @Override
        State getRejectedState() {
            return STATE_A;
        }
    };

    @Getter
    private final String value;

    State(String value) {
        this.value = value;
    }

    private static final Map<String, State> CACHE;

    static {
        CACHE = Arrays.stream(State.values()).collect(Collectors.toMap(State::getValue, Function.identity()));
    }

    public static State getByValue(String value) {
        return CACHE.get(value);
    }

    
    abstract State getApprovedState();

    
    abstract State getRejectedState();
}

测试代码


package basic;

import static basic.State.STATE_B;

public class StateDemo {
    public static void main(String[] args) {
        State state = State.STATE_A;

        // 一直赞同
        State approvedState;
        do {
            approvedState = state.getApprovedState();
            System.out.println(state + "-> approved:" + approvedState);
            state = approvedState;
        } while (state != State.STATE_D);


        // 获取某个状态的赞同和拒绝后的状态
        System.out.println("STATE_B approved ->" + STATE_B.getApprovedState());
        System.out.println("STATE_C reject ->" + State.getByValue("C").getRejectedState());
        System.out.println("STATE_D reject ->" + State.getByValue("D").getRejectedState());
    }
}

输出结果:

STATE_A-> approved:STATE_B
STATE_B-> approved:STATE_C
STATE_C-> approved:STATE_D
-----
STATE_B approved ->STATE_C
STATE_C reject ->STATE_A
STATE_D reject ->STATE_A

本质上通过不同的方法调用实现自身的流转,而且赞同和拒绝定义为抽象类,可以“强迫”让状态的定义方明确自己的状态流转。

整体逻辑比较内聚,状态的定义和流转都在 State 类中完成。

2.2 外部枚举

假如该枚举是外部提供,只提供枚举常量的定义,不提供状态流转,怎么办?

我们依然可以采用 switch 的方式实现状态流转:


import static basic.State.*;

public class StateUtils {
    
    public static State getApprovedState(State currentState) {
        switch (currentState) {
            case STATE_A:
                return STATE_B;
            case STATE_B:
                return STATE_C;
            case STATE_C:
                return STATE_D;
            case STATE_D:
            default:
            throw new IllegalStateException("当前已经是终态");
        }

    }

    
    public static State getRejectedState(State currentState) {
        switch (currentState) {
            case STATE_A:
                throw new IllegalStateException("当前状态不支持拒绝");
            case STATE_B:
            case STATE_C:
            case STATE_D:
            default:
                return STATE_A;
        }
    }
}

还有更通用、更容易理解的编程方式呢(不用 switch)?

状态机的每次转换是一个 State 到另外一个 State 的映射,每个状态都应该维护赞同和拒绝后的下一个状态。

因此,我们很容易会联想到使用【链表】来存储这种关系 。

由于这里是外部枚举,无法将状态流转在枚举内部完成(定义),就意味着我们还需要自定义状态节点来表示流转,如:


import lombok.Data;

@Data
public class Statenode<T> {

    private T state;

    private StateNode<T> approveNode;

    private StateNode<T> rejectNode;
}

这样构造好链表以后,还需在工具类中要构造 State 到 StateNode 的映射(因为对于外部来说,只应该感知 State 类,不应该再去理解 StateNode ) , 提供赞同和拒绝方法,内部通过拿到赞同和拒绝对应的 StateNode 之后拿到对应的 State 返回即可。

伪代码如下:


public class StateUtils{

// 构造 StateNode 链表,和构造 cache Map 略
private Map<State, StateNode<State>> cache ;

	public State getApproveState(State current){
		StateNode<State> node = cache.get(current);
		return node == null? null: return node.getApproveNode().getState();
	}

public State getRejectState(State current){
		StateNode<State> node = cache.get(current);
		return node == null? null: return node.getRejectNode().getState();
	}

}

整体比较曲折,不如直接将赞同和拒绝定义在 State 枚举内更直观。

下面给出一种 “状态链模式” 的解决方案。

赞同和拒绝底层分别使用两个 Map 存储。

为了更好地表达每次状态的方向(即 Map 中的 key 和 value),每一个映射定义为 from 和 to 。

为了避免只有 from 没有 to ,定义一个中间类型 SemiData,只有调用 to 之后才可以继续链式编程下去,最终构造出状态链。

以下结合 Map 的数据结构,结合升级版的 Builder 设计模式,实现链式编程


package basic;

import java.util.HashMap;
import java.util.Map;

public class StateChain<T> {

    private final Map<T, T> chain;

    private StateChain(Map<T, T> chain) {
        this.chain = chain;
    }


    public T getNextState(T t) {
        return chain.get(t);
    }

    public static <V> Builder<V> builder() {
        return new Builder<V>();
    }


    static class Builder<T> {

        private final Map<T, T> data = new HashMap<>();


        public SemiData<T> from(T state) {
            return new SemiData<>(this, state);
        }


        public StateChain<T> build() {
            return new StateChain<T>(data);
        }

        public static class SemiData<T> {
            private final T key;
            private final Builder<T> parent;

            private SemiData(Builder<T> builder, T key) {
                this.parent = builder;
                this.key = key;
            }

            public Builder<T> to(T value) {
                parent.data.put(key, value);
                return parent;
            }
        }
    }

}


使用案例:


package basic;

import static basic.State.*;

public class StateUtils {

    private static final StateChain<State> APPROVE;
    private static final StateChain<State> REJECT;

    static {
        APPROVE = StateChain.<State>builder().from(STATE_A).to(STATE_B).from(STATE_B).to(STATE_C).from(STATE_C).to(STATE_D).build();
        
        REJECT = StateChain.<State>builder().from(STATE_B).to(STATE_A).from(STATE_C).to(STATE_A).from(STATE_D).to(STATE_A).build();
    }

    
    public static State getApprovedState(State currentState) {
         State next = APPROVE.getNextState(currentState);
         if(next == null){
            throw new IllegalStateException("当前已经终态");
         }
         return next;
    }

    
    public static State getRejectedState(State currentState) {
        State next =  REJECT.getNextState(currentState);
         if(next == null){
            throw new IllegalStateException("当前状态不支持驳回");
         }
         return next;
    }
}

测试方法


import static basic.State.STATE_B;

public class StateDemo {

    public static void main(String[] args) {
        State state = State.STATE_A;

        // 一直赞同
        State approvedState;
        do {
            approvedState = StateUtils.getApprovedState(state);
            System.out.println(state + "-> approved:" + approvedState);
            state = approvedState;
        } while (state != State.STATE_D);
        
        System.out.println("-------");

        // 获取某个状态的赞同和拒绝后的状态
        System.out.println("STATE_B approved ->" + StateUtils.getApprovedState(STATE_B));
        System.out.println("STATE_C reject ->" + StateUtils.getRejectedState(State.getByValue("C")));
        System.out.println("STATE_D reject ->" + StateUtils.getRejectedState(State.getByValue("D")));
    }
}

输出结果

STATE_A-> approved:STATE_B
STATE_B-> approved:STATE_C
STATE_C-> approved:STATE_D
----
STATE_B approved ->STATE_C
STATE_C reject ->STATE_A
STATE_D reject ->STATE_A

这种方式更加灵活,可定义多条状态链,实现每个链的状态各自流转。而且性能非常好。

巧妙地将状态的转换定义和 Map 的定义合二为一,既能够表意(from,to 比较明确),又能获得很好的性能(获取赞同和拒绝后的状态转化为
通过 key 取 Map 中的 value ),还有不错的编程体验(链式编程)。

以上只是 DEMO,实际编码时,可自行优化。

可能还有一些开源的包提供状态机的功能,但核心原理大同小异。

三、总结

本文结合自己的理解,给出一种推荐的有限状态机的写法。

给出了自有状态枚举和外部状态枚举的解决方案,希望对大家有帮助。

通过本文,大家也可以看出,简单的问题深入思考,也可以得到不同的解法。

希望大家不要满足现有方案,可以灵活运用所学来解决实践问题。

到此这篇关于Java实现有限状态机的推荐方案的文章就介绍到这了,更多相关Java实现有限状态机内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java实现有限状态机的推荐方案分享

本文链接: https://lsjlt.com/news/157532.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java实现有限状态机的推荐方案分享
    目录一、背景 二、推荐方式2.1 自定义的枚举 2.2 外部枚举 三、总结 一、背景 平时工作开发过程中,难免会用到状态机(状态的流转)。 如奖学金审批流程、请假审批流程、竞标流程...
    99+
    2024-04-02
  • C++有限状态机实现详解
    目录有限状态机四大要素C++函数指针实现总结前提:因为最近打算学设计模式的状态模式,但不是很明白有限状态机和状态模式之间的关系,索性用C++实现了一个简单案例复习了一下FSM,如果有...
    99+
    2024-04-02
  • React使用有限状态机的实现示例
    目录什么是有限状态机?有限状态机的示例有限状态机和软件开发、计算机科学有什么关系?举了那么多例子,但我该怎么展示在前端开发中使用状态机呢?React 应用程序中的注册表单的传统实现方...
    99+
    2024-04-02
  • C++怎么实现一个有限状态机
    本篇内容介绍了“C++怎么实现一个有限状态机”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!什么是有限状态机?简单说就是作一件事可能会经过多个...
    99+
    2023-06-03
  • Java状态机的一种优雅写法分享
    状态机是一种数学模型,对于我们业务实现有很大的帮助。 我们可以用非常多的方法实现状态机,比如用茫茫多的if-else来进行条件判断,但是这种原始的方法并不够优雅,这篇文章就来介绍一个...
    99+
    2023-05-15
    Java状态机写法 Java状态机
  • mongo分布式锁Java实现方法(推荐)
    一、分布式锁使用场景:代码部署在多台服务器上,即分布式部署。多个进程同步访问一个共享资源。二、需要的技术:数据库:mongojava:mongo操作插件类 MongoTemplate(maven引用),如下:<!--mongodo开始...
    99+
    2023-05-31
    mongo 分布式锁 java
  • JS前端实现fsm有限状态机实例详解
    目录引言举个栗子从零开始获取状态状态改变transition实现fsm状态机实现钩子函数完整代码总结引言 我们平时开发时本质上就时对应用程序的各种状态进行切换并作出相应处理,最直接的...
    99+
    2024-04-02
  • 利用MongoDB实现实时推荐系统的经验分享
    随着互联网的发展,人们的生活越来越数字化,个性化需求也越来越强烈。在这个信息爆炸的时代,用户往往面对海量的信息无从选择,所以实时推荐系统的重要性愈发凸显出来。本文将分享利用MongoDB实现实时推荐系统的经验,希望能为开发者们提供一些启发和...
    99+
    2023-11-03
    MongoDB 经验分享 实时推荐系统
  • PHP编码实现手机浏览限制的方法分享
    PHP编码实现手机浏览限制的方法分享 随着移动互联网的飞速发展,越来越多的网站开始关注手机端用户的访问体验。有些网站可能希望限制只允许PC端用户访问,或者想要对手机端用户进行一些特定的...
    99+
    2024-03-06
    php 限制 浏览
  • Android实现关机与重启的几种方式(推荐)
    下面我们来探究Android如何实现关机,重启;在Android中这种操作往往需要管理员级别,或者root Android实现的方式如下几种: 默认的SDK并没有提供应用开发者...
    99+
    2022-06-06
    Android
  • Java状态设计模式实现对象状态转换的优雅方式
    目录介绍实现总结优点缺点应用场景介绍 Java状态模式(State Pattern)是一种面向对象的设计模式,它将对象的状态封装成独立的状态对象,并将对象的行为与状态对象解耦,它允许...
    99+
    2023-05-17
    Java状态设计模式 状态模式实现对象状态转换
  • Java线程的状态及常用方法实例分析
    这篇文章主要介绍了Java线程的状态及常用方法实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java线程的状态及常用方法实例分析文章都会有所收获,下面我们一起来看看吧。可以通过 Thread.getSt...
    99+
    2023-06-30
  • android实现添加耳机状态图标的方法
    本文实例讲述了android实现添加耳机状态图标的方法。分享给大家供大家参考。具体如下: 原生态的android系统是没有耳机插入或未插入的状态指示的,本文就是讲解如何添加耳机...
    99+
    2022-06-06
    耳机 状态图 方法 Android
  • 了解Python中的状态机及其实现方式
    状态机是一种行为模型,它定义对象如何响应事件。在Python中,状态机通常实现为有限状态机(FSM)。FSM是一种数学计算模型,可用于设计数字逻辑电路和计算机程序。它由一组状态,及状态之间的转换以及发生转换时执行的操作组成。 有...
    99+
    2024-01-22
  • Android编程实现状态保存的方法分析
    本文实例讲述了Android编程实现状态保存的方法。分享给大家供大家参考,具体如下:当我们正在发短信的时候,已经写了几百字了,这时突然来了一个电话,我们接完电话之后,如果发现辛辛苦苦的几百字不见了,那可就火大了,而实际上这些内容都是保存了的...
    99+
    2023-05-30
    android 状态 保存
  • java实现web实时消息推送的七种方案
    目录引言什么是消息推送(push)短轮询长轮询iframe流SSE (我的方式)MQTTWebsocket自定义推送Github地址引言 做了一个小破站,现在要实现一个站内信web消...
    99+
    2024-04-02
  • Android实现关机重启的方法分享
    实现系统重启的APK需要system的权限,在AndroidManifest.xml中增加android:sharedUserId="android.uid.system",再...
    99+
    2022-06-06
    方法 Android
  • 通过MySQL开发实现数据挖掘与推荐系统的项目经验分享
    在当前互联网时代,大数据的应用已经成为许多企业的重要战略。数据挖掘作为一种重要的数据分析技术,帮助企业从庞大的数据中挖掘出有价值的信息,为企业决策和业务发展提供支持。而推荐系统则是针对用户的个性化需求,通过分析用户的历史行为和兴趣,为用户提...
    99+
    2023-11-03
    推荐系统 数据挖掘 MySQL开发
  • java多态的实现方式有哪些
    Java中多态的实现方式有以下几种:1. 继承:子类继承父类,并重写父类的方法,通过父类引用指向子类对象实现多态。2. 接口:定义一...
    99+
    2023-08-16
    java
  • ASP 框架下的路径分布式管理解决方案有哪些值得推荐?
    ASP框架下的路径分布式管理解决方案有哪些值得推荐? 在ASP框架下,路径分布式管理是一个非常重要的问题。如果没有一个好的解决方案,那么就会导致代码的可维护性和可扩展性受到很大的影响。下面,我们将介绍几种值得推荐的路径分布式管理解决方案。 ...
    99+
    2023-10-19
    框架 path 分布式
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作