ts实现状态机

用ts学习状态模式

1. 什么是状态机

做产品的时候,我们总能遇到一些比较复杂的逻辑问题,而普通的流程图,或时序图对于对象和状态的解读缺乏直观的描述。

有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

2. 状态机图怎么画?

做需求时,需要了解以下六种元素:起始、终止、现态、次态(目标状态)、动作、条件,我们就可以完成一个状态机图了:


①现态:是指当前所处的状态。
②条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

注意事项

1、避免把某个“程序动作”当作是一种“状态”来处理,那么如何区分“动作”和“状态”?“动作”是不稳定的,即使没有条件的触发,“动作”一旦执行完毕就结束了;而“状态”是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。
2、状态划分时漏掉一些状态,导致跳转逻辑不完整。所以在设计状态机时,我们需要反复的查看设计的状态图或者状态表,最终达到一种牢不可破的设计方案。

3. 什么是状态模式
状态模式(state pattern)

定义:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

4. 如何用ts来实现状态模式

万能糖果公司想设计一款糖果自动售货机,默认发放一颗糖果,实现转动曲柄有概率发放两颗糖果的整个功能
状态图如下:


首先分析状态图,分别有

  • 四个状态,即

    • 没有 25分钱
    • 有 25分钱
    • 售出糖果
    • 糖果售罄
  • 四个行为动作,即

    • 投入25分钱
    • 退回25分钱
    • 转动曲柄
    • 发放糖果

设计:
1. 我们将 所有的行为动作封装在接口内,每个动作都对应一个方法
2. 每个状态实现接口状态类,这些类将负责在对应的状态下进行机器的行为。
3. 将动作委托都状态类。

好了分析了设计方法,代码如下
接口

interface State {
    insertQuarter:void,
    ejectQuarter:void,
    turnQuarter:void,
    dispense():void
}

机器

class machine {
    private noQuarterState:State,
    private hasQuarterState:State,
    private soldState:State,
    private soldOut:State,
    private winnerState:State,
    private state:State,
    private count:number
    constructor(count:number){
        this.count = count;
        this.noQuarterState = new NoQuarterState(this)
        this.hasQuarterState = new HasQuarterState(this)
        this.soldState = new SoldState(this)
        this.soldOut = new SoldOut(this)
        this.winnerState = new WinnerState()
        if(count > 0)
        {
            this.setState(this.getNoQuarterState())
        }else{
            this.setState(this.getSoldOutState())
        }
    }
    public setState(state:state){
        this.state = state
    }
    public getState(){
        return this.state
    }
    public getNoQuarterState(){
        return this.noQuarterState
    }
    public getHasQuarterState(){
        return this.hasQuarterState
    }
    public getSoldState(){
        return this.soldState
    }
    public getSoldOutState(){
        return this.soldOut
    }
    public getWinnerState(){
        return this.winnerState
    }
    public getCount(){
        return this.count
    }
    public releaseBall(){
        comsole.log("发放糖果")
    }
    public insertQuarter(){  //将投币脱硝给当前状态
        this.state.insertQuarter()
    }
    public ejectQuarter(){  //将退钱脱销给当前状态
        this.state.ejectQuarter()
    }
   public turnCrank(){    //将售糖果脱销给当前状态
        this.state.turnCrank() 
   }
    public dispense(){   //将发放糖果脱销给当前状态
        this.state.dispense()
    }
}

每个状态


class NoQuarterState implements State { // 没有 25 分钱
    private gumballMachine:GumballMachine
    public name: string
    constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
        this.gumballMachine = gumballMachine
        this.name = '没有 25 分钱'
    }
    insertQuarter() {
        this.gumballMachine.setState(this.gumballMachine.getHasQuarterState())
    }
    ejectQuarter() { // 如果没给钱就不能要求退钱
        console.log('你没有投币。')
    }
    turnCrank(){ // 如果没给钱就不能要求售出糖果
        console.log('你摇动了曲柄,但是没有投币。')
    }
    dispense(){ // 如果没给钱就不能发放糖果
        console.log('你需要先投币')
    }
}

class HasQuarterState implements State { // 有 25 分钱
    private gumballMachine:GumballMachine
    public name: string
    constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
        this.gumballMachine = gumballMachine
        this.name = '有 25 分钱'
    }
    insertQuarter() {
        console.log('你已经投过币了,不需要再投币了。')
    }
    ejectQuarter() {
        console.log('返还 25 分钱')
        this.gumballMachine.setState(this.gumballMachine.getNoQuarterState())
    }
    turnCrank(){
        console.log('售出糖果')
        let winner = Math.random() <= 0.1
        if (winner) {
            this.gumballMachine.setState(this.gumballMachine.getWinnerState())
        } else {
            this.gumballMachine.setState(this.gumballMachine.getSoldState())
        }
    }
    dispense(){ // 这是一个不应该发生的动作
        console.log('还未摇动摇杆,无法售出糖果。')
    }
}

class SoldState implements State {
    private gumballMachine:GumballMachine
    public name: string
    constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
        this.gumballMachine = gumballMachine
        this.name = '售出糖果'
    }
    insertQuarter() { // 在售出糖果的时候不能再投币了
        console.log('已经投过币了,请等待糖果售出。')
    }
    ejectQuarter() {
        console.log('对不起,你已经摇动了售货曲柄,无法退钱了...')
    }
    turnCrank(){
        console.log('你已经摇动过售货曲柄了,同一次购买不能售货两次。')
    }
    dispense(){ // 发放糖果
        this.gumballMachine.releaseBall() // 发放糖果
        // 改变至下一个状态
        if (this.gumballMachine.getCount() > 0) { // 库存为正
            this.gumballMachine.setState(this.gumballMachine.getNoQuarterState()) // 设置到未投币状态
        } else {
            this.gumballMachine.setState(this.gumballMachine.getSoldOutState()) // 库存等于小于零 设置到售罄状态
        }
    }
}

class SoldOutState implements State {
    private gumballMachine:GumballMachine
    public name: string
    constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
        this.gumballMachine = gumballMachine
        this.name = '售罄'
    }
    insertQuarter() {
        console.log('糖果已售罄不能投币。')
    }
    ejectQuarter() {
        console.log('对不起,糖果已售罄你无法投币,也无法退币。')
    }
    turnCrank(){
        console.log('你已经摇了曲柄,但是糖果已售罄。')
    }
    dispense(){
        console.log('糖果已售罄,无法发放糖果.')
    }
}

赢家


class WinnerState implements State {
    private gumballMachine:GumballMachine
    public name: string
    constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
        this.gumballMachine = gumballMachine
        this.name = '赢家'
    }
    insertQuarter() { // 在售出糖果的时候不能再投币了
        console.log('已经投过币了,请等待糖果售出。')
    }
    ejectQuarter() {
        console.log('对不起,你已经摇动了售货曲柄,无法退钱了...')
    }
    turnCrank(){
        console.log('你已经摇动过售货曲柄了,同一次购买不能售货两次。')
    }
    dispense(){
        console.log('恭喜你,你获得了赢家状态,如果库存充足的话,你讲获得额外的一颗糖!')
        this.gumballMachine.releaseBall() // 发放第一颗糖果
        // 改变至下一个状态
        if (this.gumballMachine.getCount() > 0) { // 库存为正
            this.gumballMachine.releaseBall() // 发放第二课糖果
            if (this.gumballMachine.getCount() > 0) {
                this.gumballMachine.setState(this.gumballMachine.getNoQuarterState()) // 设置到未投币状态
            } else {
                this.gumballMachine.setState(this.gumballMachine.getSoldOutState()) // 库存等于小于零 设置到售罄状态
            }
        } else {
            this.gumballMachine.setState(this.gumballMachine.getSoldOutState()) // 库存等于小于零 设置到售罄状态
        }
    }
}

测试

// 实例化糖果售货机

let gumballMachine = new GumballMachine(10) // 放入 10 颗糖果

function test(){

    // 投入一枚25分钱的硬币
    console.log('投入一枚25分钱的硬币')

    gumballMachine.insertQuarter()

    // 转动曲柄
    console.log('转动曲柄')
    gumballMachine.turnCrank()

    console.log(`当前库存${gumballMachine.getCount()}`)
}

test()

let interval: number

interval = setInterval(()=>{
    if (gumballMachine.getCurrentState().name === '售罄') {
        console.log('售罄')
        clearInterval(interval)
    } else {
        test()
    }
}, 5000);

浏览器输出结果:


VM153:329 投入一枚25分钱的硬币
VM153:332 转动曲柄
VM153:233 售出糖果
VM153:183 发放糖果
VM153:334 当前库存4
1
VM153:329 投入一枚25分钱的硬币
VM153:332 转动曲柄
VM153:233 售出糖果
VM153:183 发放糖果
VM153:334 当前库存3
VM153:329 投入一枚25分钱的硬币
VM153:332 转动曲柄
VM153:233 售出糖果
VM153:307 恭喜你,你获得了赢家状态,如果库存充足的话,你讲获得额外的一颗糖!
VM153:183 发放糖果
VM153:183 发放糖果
VM153:334 当前库存1
VM153:329 投入一枚25分钱的硬币
VM153:332 转动曲柄
VM153:233 售出糖果
VM153:307 恭喜你,你获得了赢家状态,如果库存充足的话,你讲获得额外的一颗糖!
VM153:183 发放糖果
VM153:334 当前库存0
VM153:340 售罄
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352