首先声明一点,这个模式是我目前见过最好用(本人观点),但是也是最难理解的一个(本人观点)。 所以大家需要做好心理准备,如果,对这个模式没有特别强烈的需求,比如: 我有一个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->autovar 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~.

FSM状态机之状态模式相关推荐

  1. 在游戏中看状态机与状态模式

    状态机与状态模式 状态机 最早接触状态机这个词来自编译原理的学习,在词法分析中,通过有限状态机来进行单词识别.状态机在里面被定义为一个数学模型,一个五元组. 截图来自维基百科 对于Android开发者 ...

  2. android 状态机的作用,Android 状态机、状态模式 基础框架实现

    一.StateMachine 要解决的问题 从设计模式的角度来讲,状态模式和策略模式类图是一样的,所以工作原理也类似.但相比较于策略模式,状态模式需要管理好状态树,以及维护状态对象的生命周期. 所以, ...

  3. 设计模式 - 状态模式(状态机)

    有限状态机(英语:finite-state machine,缩写:FSM) 有限状态机又称有限状态自动机(英语:finite-state automation,缩写:FSA),简称状态机,是表示有限个 ...

  4. 设计模式:状态模式与状态机

    文章目录 前言 状态模式 实现 状态机 概念 状态 事件 动作 实现技巧 实战 分析 总结 前言 在工作时遇到了这样一个需求: 控制消毒柜: 1. 当柜门打开时,关闭消毒,并重置已消毒时间: 2. 当 ...

  5. 状态模式的介绍及状态机模型的函数库javascript-state-machine的用法和源码解析

    文章大体就两部分: 状态模式 状态机模型的函数库javascript-state-machine的用法和源码解析 场景及问题背景: 我们平时开发时本质上就是对应用程序的各种状态进行切换并作出相应处理. ...

  6. 有限状态机FSM详解(2)——采用状态模式的FSM

    [状态模式] 状态模式主要解决的是当控制一个对象状态转换的条件判断过于复杂时,把判断逻辑转移到表示不同状态的一系列类中,从而简化复杂的逻辑判断.(如果状态转换条件判断很简单,就没必要用状态模式了) [ ...

  7. 状态模式 处理订单状态_将状态机模式实现为流处理器

    状态模式 处理订单状态 在我的上一个博客中,我说过我真的以为某些"四人行"(GOF)模式已经过时了,如果不是过时的话肯定不受欢迎. 特别是我说过StateMachine没什么用,因 ...

  8. c语言设计模式--状态模式(状态机)

    模式动机 状态模式(状态机)是嵌入式开发中最重要.最核心的设计模式之一,毫不夸张的说,是否熟练掌握状态模式,很大程度上直接决定了嵌入式工程师的代码掌控能力.在嵌入式开发里面,几乎80%以上的程序都有状 ...

  9. 【设计模式】状态模式(状态机)

    先介绍下状态机,状态机一般由三部分组成:状态(State).事件(Event).动作(Action).其中,事件也称为转移条件(Transition Condition).事件 触发 状态的转移及动作 ...

最新文章

  1. dsp-asic-fpga
  2. easyexcel中的常用注解
  3. 17行代码AC_51Nod - 2133 排队接水(贪心)
  4. WEB安全基础-WEB介绍
  5. jqgrid本地数据例子_FMS财务系统:日常数据核对与处理
  6. 如何判断一个点是否在多边形内?
  7. 魔兽名字显示服务器,魔兽世界怀旧服服务器名称
  8. 热烈欢迎茂名高级技工学校毕业生参加我司技术工程师岗前实训
  9. poj 3687(拓扑排序)
  10. 大端模式和小端模式的详细区别
  11. Java项目:springboot私人牙医管理系统
  12. 银行各个岗位及薪酬排名出炉(供参考)
  13. MySQL数据库基础教程
  14. 解决Win10家庭版没有‘本地用户和组’问题
  15. Redis 缓存穿透、击穿、雪崩 解决方法
  16. 一些值得细细品味的书籍
  17. idea中如何删除工程
  18. 中国大学MOOC第五章测试题答案
  19. HiCPro安装及使用
  20. cisco服务器修复模式,从映像损坏或缺失或 Rommon 模式中恢复 Cisco IOS Catalyst 4500/4000 系列交换机...

热门文章

  1. python编程培训-什么是Python?老男孩Python编程培训
  2. python使用符号 表示单行注释-Python注释(多行注释和单行注释)用法详解
  3. python 的标准库模块glob使用教程,主要为glob.glob()使用与glob.iglob()使用
  4. lua学习笔试之迭代器和通用for
  5. Button的使用(十三):设置可见性
  6. 题目1171:C翻转
  7. 题目1159:坠落的蚂蚁
  8. rest-framework 视图
  9. MapReduce实现共同朋友问题
  10. std::string用法总结