【KTips】在Kotlin中实现一个十分简单的自循环状态机

在平日的编码生活中,你有没有遇到过需要通过状态机来实现的逻辑呢?一个状态的轮转、事件订阅的通讯处理等。 状态机的实现方式千变万化,这里我会为你介绍一个简单的自循环状态机实现。

简单介绍

在开始之前,我先说明一下这个所谓的 "自循环状态机" ,避免歧义。这个词儿是我自己编出来的, 意为这个状态机中的 动作变换 均由状态本身来完成。

用大家都喜欢的大白话来讲,就是 "状态" 是一个 class,它自己包括了它的 "行为" ------ 也就是一个可执行方法, 然后这个方法的返回值便是下一个将被 "变换""状态"

文字表述还是缺乏力量,接下来便用代码来为你展示。

实现

类型定义

让我们来定义状态机中不断轮转的状态

Kotlin 复制代码
public abstract class State<S : State<S>> {
    
    /**
     * 行为.
     * @return 下一个状态,或结束循环。 
     */
    public abstract operator fun invoke(): S?
}

那么最基础的抽象定义便结束了。很简单不是吗?

!info\] 根据业务需求,`invoke` 也可以适当地调整,比如让它变为 `suspend` 可挂起的。

自循环扩展

定义完了非常简单的状态 类型 State,接下来就是实现它的 "自循环"。 State 的自循环其实很简单:提供一个初始状态,然后不断地循环, 用 invoke 的结果来代替当前的状态,直到循环结束。

但是在每一个使用状态机的地方都去写这么一些逻辑还是会有些啰嗦, 此时扩展函数便派上了用场。

我们先来看代码:

Kotlin 复制代码
public suspend inline fun <S : State<S>> S.loop(
    onEach: (S) -> Boolean = { true },
    onNext: (S?) -> S? = { it }
): S {
    var state = this // 1
    while (onEach(state)) { // 2
        state().let(onNext)?.also { state = it } ?: return state // 3
    }

    return state
}

在上面的代码中,我们在 1 处定义了初始的状态,也就是 loopreceiver 参数 S, 并从 2 处开始进入循环,也就是开始了我们状态的"自循环"。

每一次循环我们都会执行当前状态 state 的行为逻辑 invoke,并将它的返回结果设置为当前状态, 如此循环往复,直到循环终止,或者行为逻辑 invoke 返回了 null,并最终向 loop 返回最后一个被执行的状态。

除了状态本身,自循环扩展函数 loop 提供了两个用来干涉循环流程的函数 onEachonNext 来使得 loop 更加灵活, 而得益于 Kotlin 的内联函数,它们几乎不会带来什么副作用。

简单用法

纸上得来终觉浅,我们用一个简单的例子来看看这个状态机是如何使用的。 我们假设有一个订阅事件的逻辑如下:

flowchart LR 开始 --> 接收事件 接收事件 --> |获取10次|接收事件 接收事件 --> 结束

那么用代码来实现的话大概就可以是这个样子:

Kotlin 复制代码
fun main() {
    Start.loop()
}

/** 三个状态的统一类型 */
sealed class MyState : State<MyState>()

/** 代表开始。 */
data object Start : MyState() {
    override fun invoke(): MyState {
        // 进入 '接收事件' 阶段,此处假设给它分配了一个id
        return Receive(Random.nextLong())
    }
}

/** 代表接收事件。有一个计数器,到达10后结束 */
data class Receive(val id: Long) : MyState() {
    var times = 0
    override fun invoke(): MyState {
        // 达到10次以上,结束此状态循环,进入结束状态
        if (times >= 10) {
            return Done
        }
        println("times = $times")

        // 递增计数,并返回自身,也就是继续保持 Receive 状态
        times++
        return this
    }
}

/** 代表结束。 */
data object Done : MyState() {
    override fun invoke(): MyState? {
        // 结束状态可以再做一些什么操作,然后返回 null 终止循环
        println("Done!")
        return null
    }
}

结尾

以上就是一个简单的自循环状态机的实现啦!怎么样,是不是很简单呢?

如果有帮助到你,我的荣幸~

相关推荐
码农幻想梦21 小时前
实验九 Restful和ajax实现
后端·ajax·restful
今天多喝热水21 小时前
SpEL(Spring Expression Language) 表达式
java·后端·spring
码农水水21 小时前
美团Java面试被问:Netty的ByteBuf引用计数和内存释放
java·开发语言·数据库·mysql·算法·面试·职场和发展
a努力。1 天前
国家电网Java面试被问:分布式Top K问题的解决方案
java·开发语言·分布式·oracle·面试·职场和发展·kafka
码农水水1 天前
浅谈 MySQL InnoDB 的内存组件
java·开发语言·数据库·后端·mysql·面试
独自破碎E1 天前
Spring Boot的多环境配置
java·spring boot·后端
Edward-tan1 天前
【玩转全栈】----Django模板语法、请求与响应
后端·python·django
what丶k1 天前
SQL三大核心查询语法(WHERE/ORDER BY/GROUP BY)综合运用指南
大数据·数据库·sql·mysql·面试
猫头鹰源码(同名B站)1 天前
基于django+vue的时尚穿搭社区(商城)(前后端分离)
前端·javascript·vue.js·后端·python·django
Engineer邓祥浩1 天前
设计模式学习(20) 23-18 中介者模式
学习·设计模式·中介者模式