我该如何正确组织这个基于 Rx 的反应式状态机?

How do I properly organize this Rx-based reactive state machine?

请注意: 尽管我在示例中使用了 Swift-lang,但我鼓励您尝试帮助我,即使您了解 RxJava 和 rxjs 或任何其他语言Rx 实现。

问题:我有一个状态机,是命令式写的。我想使用 Rx 进行响应式重写,但我正在努力想出正确的方法。

当前命令式系统如下所示:

var state: State
var metaState: MetaState

func updateMetaStateIfNeeded() {
    if Bool.random() {
        metaState = MetaState()
    }
}

func tick() -> State {
    updateMetaStateIfNeeded()

    let newState = State(currentState: state, metaState: metaState)
    self.state = newState
    return newState
}

到目前为止我想到了什么:

let tick = PublishSubject<Void>()
let metaState = BehaviorSubject<MetaState>()
let state = BehaviorSubject<State>()

let tickAfterMetaStateUpdate = PublishSubject<Void>()

tick -> withLatestFrom metaState -> map to new metaState if update needed or the old one -> put result into metaState AND tickWithUpdatedMetaState

tickAfterMetaStateUpdate -> withLatestFrom state AND metaState -> map State(currentState: state, metaState: metaState) -> put result into state

(The users of this API subscribe to state subject, which will be updated every tick)

我对我得出的这个结果不满意。我想知道是否可以使用 scanreduce 重写它,这样就没有使用 Subjects 并且它是一个普通的 Observable。

@Daniel T. 回答后更新:

哇,这真是正确的解决方案!我已经准备好分析:

因此,有了这种理解,我使用 2 次扫描重写了系统:

let state = tick
    .scan(MetaState()) { currentMetaState, void in
        if Bool.random() {
            return MetaState()
        }
        return currentMetaState
    }
    .scan(State()) { currentState, currentMetaState in
        State(currentState: state, metaState: metaState)
    }

WINRAR! Mind_blown.jpg!实际上,现在回想起来似乎很明显...:) 谢谢,谢谢,谢谢!

嗯...是的,scan 是实现 state machine 的首选运算符。为此,您需要一组有限的状态(通常实现为结构,但也可以是枚举),一个起始状态(通常使用 State 结构上的默认构造函数实现),一个输入字母表(通常实现为枚举,通常称为事件、命令或动作的某些变体)和一个转换函数(传递给 scan 的闭包。)

所以这里真正的问题是改变状态的 commands/actions 是什么?大多数时候,操作是由用户操作产生的,但您的问题并没有真正说明操作是什么,输入字母表是什么...

遗憾的是,问题中没有足够的信息来提供比这更多的细节。

--更新--

抱歉,您的更新不正确。传递给 scan 的闭包应该是纯的,而第一个肯定不是。如果 MetaState.init() 是纯的,则 Bool.random() 不是。

唯一应该将不纯的闭包传递给 Observable 的时候是在创建新的 Observable、使用最终结果时,或者在 do.

对您所拥有内容的简单修复如下所示:

let state = tick
    .flatMap { // this closure is an Observable factory. You can do impure things here.
        Observable.just(Bool.random() ? MetaState() : nil)
    }
    .scan(State()) { currentState, metaState in // this closure should be pure.
        if let metaState = metaState {
            return State(currentState: currentState, metaState: metaState)
        }
        else {
            return currentState
        }
    }

这确实是一个小改动,但它更清楚地显示了代码的意图,并明确了哪些可以进行单元测试,哪些不能进行单元测试。