状态机设计模式_JavaScript 设计模式实践(3)- 状态模式

88d23ca9223ccad7a397869f3580debf.png

最近接了一个需要PK玩法的直播间内挂件,用户可以发起PK进行比赛,比赛过程中实时更新数据,结束之后展示结算并重新等待主播发起PK,一个小型的带交互流程任务。

一开始思路是上来就是淦,React,肯定能实现,定义function, 引入 useState 一把梭, 写着写着忽然觉得有点失控,因为我竟然无法很好的解耦每个状态的逻辑,靠React原生state做区分不同视图&不同行为很罗嗦,判断逻辑写了一堆(策略模式这时候也无法解决我的问题了,因为我暂时没办法统一处理所有切换策略发生的地方)。 而且切换流程稍微复杂,所以才会有力不从心的感觉。

每当遇到这种时候,慢即是快,硬着莽写出来的代码肯定bug很多。于是稍微整理了下,将整个流程的状态切换画了一下。

9edcd7fceff8cadbdcb27add2b697d15.png

绘制状态流程图后发现所有状态都是能预测出来的,所以我想到了能否用状态模式来解决这种问题。

运用状态模式首先需要知道我们切换不同的状态,是想让哪些行为发生变更。在这里我期望的是根据不同状态变更数据源和渲染函数的行为。之所以有这两个是因为:由于是实时请求,所以目前我们底层的数据源由一个轮询实现,这个轮询和渲染函数解耦,会自动和服务器协商调整更新频率,轮询器同时会将数据送给这个渲染函数,也就是渲染函数里的React组件仅负责渲染。

此时我每个状态可以封装如下

const getInitState = () => {
    return {
        fetch: fetchInitState,
        render: renderInitPage
    }
}

const getWaittingState = () => {
    return {
        fetch: fetchWaittingState,
        render: renderWaittingPage
    }
}

const getPKState = () => {
    return {
        fetch: fetchPKState,
        render: renderPKPage
    }
}

const getResultState = () => {
    return {
        fetch: fetchResultState,
        render: rendeResultState
    }
}

// fetch: () => fetch('/api/xxx') 单次请求,轮询器会以此用来轮询数据
// render: () => ReactElement  当数据来到之后使用这个渲染页面

子状态封装好了之后,我们需要有个统一的状态机使用这些状态

function StateMachine(state, onStateChange) {
    this.state = this.setState(state || getInitState);
    this.onChange= onStateChange;
}

StateMachine.prototype.setState = function (getNewState) {
    this.state = getNewState(this);
    // restart & refresh page immediately
    this.onChange();
}

StateMachine.prototype.fetch = function() {
    return this.state.fetch();
}
StateMachine.prototype.render = function() {
    return this.state.render();
}

这里我将StateMachine的实例传入了每个getState 函数中,目的是为了让每个State可以自由地切换到想要的State, 比如getInitState的实现如下

const getInitState = (stateMachine) => {
    function renderInitPage({ data }) {
        const onClick = () => {
            stateMachine.setState(getWaittingState);
        };
        
        return (
          <div onClick={onClick}> click me </div>
        )
    }

    return {
        fetch: fetchInitState,
        render: renderInitPage
    }
}

至于使用方,则使用起来就清晰很多, 我将额外逻辑剥离,基本上一个程序骨架变换如下。

function APP() {
    const [data, setData] = useState(null);

    const timerRef = useRef(-1);
    const stateMachineRef = useRef(null);

    const onStateChange = useCallback(() => {
        clearInterval(timerRef.current);

        timerRef.current = setInterval(() => {
            stateMachineRef.current.fetch()
                .then(setData);
        }, []);
    }, []);

    useEffect(() => {
        stateMachineRef.current = new StateMachine(null, onStateChange);

        return () => {
            clearInterval(timerRef.current);
        }
    }, []);

    return stateMachineRef.render({ data });
}

这样的好处是未来我如果想新增一种状态可以以一种非侵入原有逻辑的形式添加新的状态。


比如在上面的状态中,需求发生变更,需要新增一种状态,需要玩家等待过程中可能随机进入乱斗模式。。。(屁股)

7ac32f0b077a911f4adb213f1e195484.png

在这种设计模式基础上,相信解决思路很清晰,很容易的就能独立的写出相应状态逻辑,而且将变更的可能性减少到最小规模。

最后补上状态模式原语

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类

167873a4db6d7e82dd2853ca4e1ff6dc.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值