资讯专栏INFORMATION COLUMN

FSM状态机之状态模式

k00baa / 363人阅读

摘要:要注意这里的一个状态行为因为这个词是状态模式中最重要的个概念。考虑到这点,聪明的在中推出了状态机这个伪函数,能够帮助我们快速实现状态化。这里就引入了状态机这个概念,以及和他对应的状态表。

 首先声明一点,这个模式是我目前见过最好用(本人观点),但是也是最难理解的一个(本人观点)。 所以大家需要做好心理准备,如果,对这个模式没有特别强烈的需求,比如: 我有一个Button,我按次数点击它,他会触发不同的状态 等等这样的,可以学习一下其他的模式。但是!!! 如果你看了我这篇文章,被我前面说的话吓到了,那么就继续往下看,其实,状态模式是最好用,也是最容易掌握的一个。

大话状态模式

上面已经提到了,状态模式其实就是,一个事物的内部状态的改变,产生不同的行为。 要注意这里的

"一个","状态","行为". 因为这3个词是状态模式中最重要的3个概念。

同样,举个栗子

大家家里都有空调吧,remoter应该都用过。 首先我们拿一个简单的使唤。 就拿 on/off 键吧。首先,遥控器会记录当前的状态(假设他会这样),如果是on, 当你点击 他会发出off的信号,如果你是off,则会发出on的信号.我们用程序说明一下.

var switches = (function(){
    var state = "off";
    return function(){
        if(state === "off"){
            console.log("打开空调");
            state = "on";
        }else if(state === "on"){
            console.log("关闭空调");
            state = "off";
        }
    }
})();
document.querySelector(".switch").addEventListener("click",function(){
    switches();  //模仿你打开/关闭空调的状态
},false)

很简单吧,一个闭包+一个变量 就可以构成一个 状态机,是不是超级神奇呢?
恩,看到这里,聪明的人会,会心一笑,然后继续往下看。
是个屁。
想想,如果空调开关要是都只有两种状态,尼玛谁用啊!!!
满是turn on/off. 就跟写2进制一样。实话说,哥汇编学的差,所以也十分不愿意直视二进制,你让我使唤开关跟写汇编似的,操。 0001是开,0000是关,0010是加热模式,0011是制冷模式。

所以,海尔,美的考虑到国情,制造了比较人性化的remoter。
现在,如果我们使用上面那种模式来写,切换模式的switch。

var switches = (function(){  //auto->hot->cold->wind->dry->auto
    var state = "auto";
    return function(){
        if(state === "auto"){
            console.log("制热");
            state = "hot";
        }else if(state === "hot"){
            console.log("制冷");
            state = "cold";
        }else if(state === "cold"){
            console.log("送风");
            state = "wind";
        }else if(state === "wind"){
            console.log("除湿");
            state = "dry";
        }else if(state === "dry"){
            console.log("自动");
            state = "auto";
        }
    }
})();
document.querySelector(".switch").addEventListener("click",function(){
    switches();  //模仿你切换空调的模式
},false);

呵呵呵~ 功能是实现了,不过代码,又被if语句给rape的。 性能的强奸犯,阅读的杀手 恐怕就算if语句了. 所以,为了不犯罪,我们需要优化我们的状态模式。

高级状态模式

其实,这个状态模式的写法和命令模式有着异曲同工的妙处。即,中间有个状态仓库,然后分别将命令转发给对应的执行类。
总结一下。 高级状态模式需要有,状态仓库,状态类,状态执行者,这3个要点。 对应着我们的,”一个“,"状态","行为". 一个仓库,不同的状态,不同的执行。
just do it.

//定义状态
var Auto= function(button){
    this.turn = button;
}
Auto.prototype.press= function(){
    console.log("制热");
    this.turn.setState("hot");
}
var Hot = function(button){
    this.turn = button;
}
Hot.prototype.press= function(){
    console.log("制冷");
    this.turn.setState("cold");
}
var Cold = function(button){
    this.turn = button;
}
Cold.prototype.press= function(){
    console.log("送风");
    this.turn.setState("wind");
}
var Wind = function(button){
    this.turn = button;
}
Wind.prototype.press= function(){
    console.log("除湿");
    this.turn.setState("dry");
}
var Dry = function(button){
    this.turn = button;
}
Dry.prototype.press= function(){
    console.log("自动");
    this.turn.setState("auto");
}
//定义状态仓库
var Remoter = function(){
    this.auto = new Auto(this);
    this.hot = new Hot(this);
    this.cold = new Cold(this);
    this.wind = new Wind(this);
    this.dry = new Dry(this);
    this.state = "auto";
}
Remoter.prototype.setState = function(state){
    this.state=state;
}
Remoter.prototype.press = function(){
    this[this.state].press();  //执行对应状态的press
}
Remoter.prototype.init = function(){  //定义执行者
    document.querySelector(".switch").addEventListener("click",()=>{
        this.press();
    },false);
}
new Remoter.init();  //初始化

上面那种就是一个比较模式化的写法,而且,可复用,可添加。 比上面那种的逼格不知道高到哪里去,但是,实现成本也是挺大的。考虑到这点,聪明的ECMA-262在es6中推出了状态机这个伪函数,能够帮助我们快速实现状态化。
Duang~
就是generator函数。 目前FF,edge,chrome 最新版本已经支持。不过可以使用babel进行转化.
我们使用generator进行重构.
我比较懒,我们就先实现前3个模式的转化吧。

var auto = function(){
    console.log("自动");
}
var hot = function(){
    console.log("制热");
}
var cold = function(){
    console.log("制冷");
}
function* models(){
    for(var i = 0,fn,len=arguments.length;fn = arguments[i++];){
        yield fn();
        if(i===len){
            i = 0;
        }
    }
}
var exe = models(auto,hot,cold);  //按照模式顺序排放
document.querySelector(".switch").addEventListener("click",function(){
    exe.next();
},false);

已经没有了if来进行分支判断,效果也是蛮不错的。 关于generator的用法,还有进程控制,这些都是比较高级的用法,有兴趣的同学可以参考 阮老师的 es6讲解. 但是,推荐还是使用,一个仓库,不同状态,不同行为,这样函数对象式的写法,扩展性比较强。主要原因是因为,generator还未普及,以及设置他进程的顺序比较复杂。不过,平常本人喜欢装装逼,永远热爱新技术,所以大部分时候还是会使用generator。 总之,程序员并不是程序员,我们要有自己的核心价值观,找到自己最对的 "玛卡瑞纳",这才是我们程序员应该有的情怀。
我们仔细观察一下上面使用"类"写出来的状态模式,会发现,状态类是不是感觉可以使用享元模式优化呢?没错。因为他的方法和状态都是一致的,当然可以使用。

var obj = {
    auto(){
        console.log("自动")
        return "hot";
    },
    hot(){
        console.log("制热");
        return "cold";
    },
    cold(){
        console.log("制冷");
        return "auto"
    }
}
var State = function(){
    this.state = "auto";
    this.obj = obj;
}
State.prototype.next = function(){
    this.state = this.obj[this.state]()
}
new State().next();  //测试通过.

这只是一种比较轻巧的方法,js,最出名的就是他的动态,无拘无束,你可以天马行空的写出你的代码(但是,必须保证的你代码不会变成 凤姐 ).
但从上面的代码可以看出,如果程序里面使用return 的话,很容易会造成你函数的逻辑复杂度,所以我们这里推荐使用一个state进行保存,将this.state传入。 当然,我们并不是当参数参入了(太low),我们使用委托的技术传入,相当于给this动态织入一个函数。这个方法就叫: apply和call. 哈哈,是不是有种感觉(怎么又是你).

var obj = {
    auto(){
        console.log("自动")
        this.state = "hot";
    },
    hot(){
        console.log("制热");
        this.state = "cold";
    },
    cold(){
        console.log("制冷");
        this.state = "auto";
    }
}
var State = function(){
    this.state = "auto";
    this.obj = obj;
}
State.prototype.next = function(){
    this.obj[this.state].call(this);
}
new State().next();

没错,这下,我们不仅能将函数动态织入,而且可以直接改动state,这样可以给自己程序的扩展性加上一分。
当然,状态模式的写法还有很多,比如delegate函数的写法等等。 不过,找到自己的"玛卡瑞纳"才是最棒的。
上面只是一个线上的流式状态切换,并没有涉及很复杂的业务逻辑。但是,如果你在开发一个大型项目的时候,涉及的状态可谓是五花八门,还是以空调遥控器为例,比如,你切换到模式选择的时候,你的上下左右键,只能控制模式的切换,而不能控制风速大小,当你切换到风速选择模式的时候,同样不能控制其他的功能。 所以,如果按照上面那种 单线式的状态切换是不够的。 这里就引入了FsM(finite-state-machine),状态机这个概念,以及和他对应的状态表。
如下图

如果你是学机械的,那么这个状态切换的概念应该非常熟悉,在CH40161(一种自触发式芯片)中,你输入一个触发信号,他可以按照你这个触发信号逐步触发(我机械太渣,但意外的喜欢上计院). 在js中,gordon大神(有8个contributor)已经写出了这个状态库。有兴趣的同学可以看一看。
传送门: FSM。
其实,他里面最重要的就是"状态"和"状态切换"的规则。
先看一个demo:

var fsm = StateMachine.create({
  initial: "green",
  events: [
    { name: "warn",  from: "green",  to: "yellow" },
    { name: "panic", from: "yellow", to: "red"    },
    { name: "calm",  from: "red",    to: "yellow" },
    { name: "clear", from: "yellow", to: "green"  }
  ],
  callbacks: {
    onpanic:  function(event, from, to, msg) { alert("panic! " + msg);               },
    onclear:  function(event, from, to, msg) { alert("thanks to " + msg);            },
    ongreen:  function(event, from, to)      { document.body.className = "green";    },
    onyellow: function(event, from, to)      { document.body.className = "yellow";   },
    onred:    function(event, from, to)      { document.body.className = "red";      },
  }
});

这已经定义好了一个完整的单线式,状态切换队列。
当你触发fsm.warn(); 状态就是从green->yellow。
当你触发fsm.panic(); 状态就是从yellow->red.
...
说一下基本用法
events 里面就是你定义的状态表的规则

name: 标识,状态切换的函数名
from: 标识 为切换之前的状态
to: 标识 为切换之后的状态

callbacks 里面就是对状态和切换规则函数的定义. 这里不说的太复杂,就按照基本的讲解吧。

使用on+Name; 定义状态切换的函数
使用on+State: 定义某个状态时触发的函数

当然,还有

onbeforeevent - fired before any event
onleavestate - fired when leaving any state
onenterstate - fired when entering any state
onafterevent - fired after any event

这些比较细,这里就不做详细介绍,如果有兴趣的同学可以去github上面看一看,理解起来也不是很难。我这里介绍的我经常使用的。
所以,上面的流程就是。
使用fsm.panic() 之后。
触发顺序为: onpanic()->red();
如果你状态不对,而强行调用fsm.panic的话就会触发error函数(这里没有写). 所以,上面写的fsm 差不多已经够用了,关键看你如果组合了。 要知道,二维难度 >> 一维难度。 有一个好工具,能把你的工作量降到最低。

谈谈状态模式

说到这里,我的这篇blog大部分是介绍 一些基本原理和方法,状态模式的应用在程序设计中是非常重要的一个概念,如果你掌握了,语言只会变为你的一个工具,因为 你已经吃透了 隐藏在 语言背后的 secret. 最后还是那句话, 不要为了模式而模式,但状态模式确实是个好模式。
ending~.

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78547.html

相关文章

  • 设计模式手册之状态模式

    摘要:什么是状态模式状态模式对象行为是基于状态来改变的。原文地址设计模式手册之状态模式优缺点优点封装了转化规则,对于大量分支语句,可以考虑使用状态类进一步封装。 1. 什么是状态模式? 状态模式:对象行为是基于状态来改变的。 内部的状态转化,导致了行为表现形式不同。所以,用户在外面看起来,好像是修改了行为。 Webpack4系列教程(17篇) + 设计模式手册(16篇):GitHub地址 博...

    call_me_R 评论0 收藏0
  • 设计模式手册之状态模式

    摘要:什么是状态模式状态模式对象行为是基于状态来改变的。原文地址设计模式手册之状态模式优缺点优点封装了转化规则,对于大量分支语句,可以考虑使用状态类进一步封装。 1. 什么是状态模式? 状态模式:对象行为是基于状态来改变的。 内部的状态转化,导致了行为表现形式不同。所以,用户在外面看起来,好像是修改了行为。 Webpack4系列教程(17篇) + 设计模式手册(16篇):GitHub地址 博...

    Faremax 评论0 收藏0
  • 使用有限状态机管理状态

    摘要:集成到去使用如果想在中使用,想到比较方便的使用形式是高阶组件,需要用到有限状态机的组件传进高阶组件,就立马拥有了使用有限状态机的能力。 背景 近年来由于一些前端框架的兴起而后逐渐成熟,组件化的概念已经深入人心,为了管理好大型应用中错综复杂的组件,又有了单向数据流的思想指引着我们,Vuex、Redux、MobX等状态管理工具也许大家都信手拈来。我们手握着这些工具,不断思考着哪些数据应该放...

    hiyang 评论0 收藏0
  • JavaScript 设计模式 一些笔记

    摘要:如果非要重写父类的方法,比较通用的做法是原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖聚合,组合等关系代替。里氏替换原则通俗的来讲就是子类可以扩展父类的功能,但不能改变父类原有的功能。一有限状态机状态总数是有限的。 设计模式 抽象类 抽象类的表现 不能被实例,只能被继承 最少有一个抽象方法(多态的具体体现) // 汽车抽象类,当使用其实例对象的方法时会抛出错误...

    paulli3 评论0 收藏0
  • 游戏人工智能 读书笔记 (四) AI算法简介——Ad-Hoc 行为编程

    摘要:原文链接本文内容包含以下章节本书英文版这个章节主要讨论了在游戏中经常用到的一些基础的人工智能算法。行为树是把的图转变成为一颗树结构。根据当前游戏的环境状态得到某一个行为的效用值。 作者:苏博览商业转载请联系腾讯WeTest获得授权,非商业转载请注明出处。原文链接:https://wetest.qq.com/lab/view/427.html 本文内容包含以下章节: Chapter 2 ...

    xinhaip 评论0 收藏0

发表评论

0条评论

k00baa

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<