javascript设计模式学习之十六——状态模式
一、状态模式的定义
状态模式的关键是区分事务内部和外部的状态,事务内部状态改变往往会带来事务的行为改变。
状态模式中有意思的一点是,一般我们谈到封装,都是优先封装对象的行为,而非对象的状态。但在状态模式中刚好相反,状态模式的关键是把事务的每种状态都封装为单独的类,跟此种状态有关的行为都封装在这个类的内部。与此同时,我们还可以把状态的切换规则实现分布在状态类中,这样就有效消除了原本存在的大量条件分支语句。
二、状态模式的应用案例——文件上传
在现实中,状态模式的应用案例有很多,如文件上传程序有文件扫描、正在上传、暂停、上传成功、上传失败这几种状态,音乐播放器有加载中、正在播放、暂停、播放完毕这几种状态。点击同一个按钮,在上传中和暂停状态下的行为表现是不一样的,同时他们的class也不一样。
在文件上传中,涉及到两个控制按钮:第一个用于暂停和继续上传,第二个用于删除文件;文件在不同的状态下,点击这两个按钮,发生的行为也不同:
1)文件在扫描过程中,是不能进行任何操作的,既不能暂停也不能删除文件,只能等待扫描完成。扫描完成后,根据文件的md5进行判断,如果文件已经存在于服务器中,则直接跳转到上传完成状态。如果文件的大小超过允许上传的最大值,或者文件已经损坏,则跳转到上传失败状态。
2)上传过程中可以点击暂停按钮来暂停上传,暂停后点击同一个按钮会继续上传。
3)扫描和上传过程中,点击删除按钮无效,只有在暂停、上传完成、上传失败之后,才能删除文件。
文件上传是一个异步的过程,在上传过程中,上传插件会不停地调用javascript提供的一个全局函数window.external.upload,来通知javascript目前的上传进度,控件会把当前的文件状态作为参数state塞进window.external.upload.这里使用setTimeout来模拟文件的上传进度,以下代码模拟上传插件的实现:
- //上传插件对象
- var plugin = (function() {
- var plugin = document.createElement('embed');
- plugin.style.display = 'none';
- plugin.type = 'application/txftn-webkit';
- //插件扫描
- plugin.sign = function() {
- console.log('plugin开始进行文件扫描');
- };
- plugin.pause = function() {
- console.log('plugin暂停文件扫描');
- };
- plugin.uploading = function() {
- console.log('plugin开始文件上传');
- };
- plugin.del = function() {
- console.log('plugin删除文件上传');
- };
- plugin.done = function() {
- console.log('plugin文件上传完成');
- };
- document.body.appendChild(plugin);
- return plugin;
- })();
如果编写传统代码实现文件上传,代码如下:
- //采用传统方法实现文件上传
- //实现上传类
- var Upload = function(fileName) {
- this.fileName = fileName;
- this.plugin = plugin;
- this.button1 = null; //控制继续、暂停上传等
- this.button2 = null; //控制上传文件删除等
- this.curState = 'sign'; //设置初始状态为文件扫描
- };
- Upload.prototype.init = function() {
- this.div = document.createElement('div');
- this.div.innerHTML = '<span>文件名称:' + this.fileName + '</span><button id="button1">扫描中</button><button id="button2">删除</button>';
- document.body.appendChild(this.div);
- this.button1 = document.getElementById('button1');
- this.button2 = document.getElementById('button2');
- this.bindEvent();
- };
- Upload.prototype.changeState = function(state) { //负责切换状态的具体行为
- this.curState=state;
- switch (state) {
- case 'sign':
- this.plugin.sign();
- this.button1.innerHTML = '扫描中,任何操作无效';
- break;
- case 'uploading':
- this.plugin.uploading();
- this.button1.innerHTML = '正在上传,点击暂停';
- break;
- case 'pause':
- this.plugin.pause();
- this.button1.innerHTML = '已暂停,点击继续上传';
- break;
- case 'done':
- this.plugin.done();
- this.button1.innerHTML = '文件上传完成';
- break;
- case 'error':
- this.button1.innerHTML = '文件上传失败';
- break;
- case 'del':
- this.plugin.del();
- this.div.parentNode.removeChild(this.div);
- console.log('删除完成');
- break;
- }
- };
- Upload.prototype.bindEvent = function() {
- var self = this;
- this.button1.onclick = function() {
- if (self.curState === 'sign') {
- console.log('扫描中,点击无效...');
- } else if (self.curState === 'uploading') { //上传中,切换到暂停样式
- self.changeState('pause');
- } else if (self.curState === 'pause') { //暂停中,切换到上传模式
- self.changeState('uploading');
- } else if (self.curState === 'done') {
- console.log('文件已经上传,点击无效...');
- } else if (self.curState === 'error') {
- console.log('文件上传失败,点击无效...');
- }
- };
- this.button2.onclick = function() {
- if (self.curState === 'done' || self.curState === 'error' || self.curState === 'pause') {
- self.changeState('del'); //以上三种状态下可以删除
- } else if (self.curState === 'sign') {
- console.log('文件正在扫描中,不能删除');
- } else if (self.curState === 'uploading') {
- console.log('文件正在上传中,不能删除');
- }
- };
- };
上面完成了一个简单的文件上传程序。当然这是一个反例,程序中充斥着大量的if-else语句,状态和行为都被耦合到一个巨大的方法里,难以进行修改和扩展。
测试:
- var uploadObj=new Upload('java疯狂讲义');
- uploadObj.init();
- window.external.upload=function(state){
- uploadObj.changeState(state);
- };
- window.external.upload('sign');
- setTimeout(function(){
- window.external.upload('uploading');
- },);
- setTimeout(function(){
- window.external.upload('done');
- },);
运用状态模式重构以上代码:
- //上传插件对象
- var plugin = (function() {
- var plugin = document.createElement('embed');
- plugin.style.display = 'none';
- plugin.type = 'application/txftn-webkit';
- //插件扫描
- plugin.sign = function() {
- console.log('plugin开始进行文件扫描');
- };
- plugin.pause = function() {
- console.log('plugin暂停文件扫描');
- };
- plugin.uploading = function() {
- console.log('plugin开始文件上传');
- };
- plugin.del = function() {
- console.log('plugin删除文件上传');
- };
- plugin.done = function() {
- console.log('plugin文件上传完成');
- };
- document.body.appendChild(plugin);
- return plugin;
- })();
- //使用状态模式重构文件上传代码
- var Upload = function() {
- this.plugin = plugin;
- this.fileName = fileName;
- this.button1 = null;
- this.button2 = null;
- //构建各个状态类
- this.signState = new SignState(this);
- this.uploadingState = new UploadingState(this);
- this.pauseState = new PauseState(this);
- this.doneState = new DoneState(this);
- this.errorState = new ErrorState(this);
- this.curState = this.signState;
- };
- Upload.prototype.init = function() {
- this.div = document.createElement('div');
- this.div.innerHTML = '<span>文件名称:' + this.fileName + '</span><button id="button1">扫描中</button><button id="button2">删除</button>';
- document.body.appendChild(this.div);
- this.button1 = document.getElementById('button1');
- this.button2 = document.getElementById('button2');
- this.bindEvent();
- };
- Upload.prototype.bindEvent = function() {
- var self = this;
- this.button1.onclick = function() {
- self.curState.clickHandler1();
- };
- this.button2.onclick = function() {
- self.curState.clickHandler2();
- };
- };
- Upload.prototype.sign=function(){
- this.plugin.sign();
- this.curState=this.signState;
- };
- Upload.prototype.uploading=function(){
- this.button1.innerHTML='正在上传,点击暂停';
- this.plugin.uploading();
- this.curState=this.uploadingState;
- };
- Upload.prototype.pause=function(){
- this.button1.innerHTML='已暂停,点击继续上传';
- this.plugin.pause();
- this.curState=this.pauseState;
- };
- Upload.prototype.done=function(){
- this.button1.innerHTML='上传完成';
- this.plugin.done();
- this.curState=this.doneState;
- };
- Upload.prototype.error=function(){
- this.button1.innerHTML='上传失败';
- this.curState=this.errorState;
- };
- Upload.prototype.del=function(){
- this.plugin.del();
- this.div.parentNode.removeChild(this.div);
- };
- //接下来编写各个状态类的实现
- var StateFactory = (function() {
- //这个相当于抽象类
- var State = function() {};
- State.prototype.clickHandler1 = function() {
- throw new Error('子类必须重写父类的clickHandler1方法');
- };
- Stat8e.prototype.clickHandler2 = function() {
- throw new Error('子类必须重写父类的clickHandler2方法');
- };
- return function(param) {
- var F = function(uploadObj) {
- this.uploadObj = uploadObj;
- };
- F.prototype = new State();
- for (var i in param) {
- F.prototype[i] = param[i];
- }
- return F;
- };
- })();
- var signState = StateFactory({
- clickHandler1: function() {
- console.log('扫描中,点击无效');
- },
- clickHandler2: function() {
- console.log('文件正在扫描,不能删除');
- }
- });
- var UploadingState = StateFactory({
- clickHandler1: function() {
- this.uploadObj.pause();
- },
- clickHandler2: function() {
- console.log('文件正在上传,不能删除');
- }
- });
- var PauseState = StateFactory({
- clickHandler1: function() {
- this.uploadObj.uploading();
- },
- clickHandler2: function() {
- this.uploadObj.del();
- }
- });
- var DoneState = StateFactory({
- clickHandler1: function() {
- console.log('文件上传完成,点击无效');
- },
- clickHandler2: function() {
- this.uploadObj.del();
- }
- });
- var ErrorState = StateFactory({
- clickHandler1: function() {
- console.log('文件上传失败,点击无效');
- },
- clickHandler2: function() {
- this.uploadObj.del();
- }
- });
- window.external.upload=function(){
- };
状态模式控制电灯案例:
下面已切换电灯状态这个相对简单的例子来进一步说明状态模式:
- //状态模式学习
- //以控制灯泡开关的状态为例,电灯开关开着的时候,按下开关,电灯会切换到关闭状态,反之,则切换到开启状态
- var Light=function(){
- this.state='off';
- this.button=null;
- this.stateSpan=document.getElementById('lightState');
- };
- Light.prototype.init=function(){
- var button=document.createElement('button');
- button.innerHTML='开关';
- this.button=document.body.appendChild(button);
- this.bindEvent();
- };
- Light.prototype.bindEvent=function(){
- var self=this;
- this.button.onclick=function(e){
- self.buttonWasPressed();
- };
- };
- Light.prototype.buttonWasPressed=function(){
- if(this.state==='off'){
- console.log('电灯打开了');
- this.stateSpan.innerHTML='打开';
- this.state='on';
- }else if(this.state==='on'){
- this.stateSpan.innerHTML='关闭';
- console.log('电灯关闭了');
- this.state='off';
- }
- };
- //测试
- var light=new Light();
- light.init();
以上代码中的buttonWasPressed方法显然违背了封闭-开放原则,电灯的状态除了开,关之外,还可能有多种,如强光,弱光等,每一次新增或者修改状态都需要修改buttonWasPressed方法,该方法将变得极为庞大和臃肿,以至于极难维护。
- //使用状态模式进行代码重构
- //
- function OnState(light){
- this.light=light;
- }
- OnState.prototype.buttonWasPressed=function(){
- this.light.lightSpan.innerHTML='关闭';
- this.light.curState=this.light.offState;
- };
- function OffState(light){
- this.light=light;
- }
- OffState.prototype.buttonWasPressed=function(){
- this.light.lightSpan.innerHTML='打开';
- this.light.curState=this.light.onState;
- };
- var Light=function(){
- this.button=null;
- this.lightSpan=document.getElementById('lightState');
- this.onState=new OnState(this);
- this.offState=new OffState(this);
- this.curState=this.offState;
- };
- Light.prototype.init=function(){
- var button=document.createElement('button');
- button.innerHTML='开关';
- this.button=document.body.appendChild(button);
- var self=this;
- this.button.onclick=function(){
- self.buttonWasPressed();
- };
- };
- Light.prototype.buttonWasPressed=function(){
- this.curState.buttonWasPressed();
- };
- //测试
- var light=new Light();
- light.init();
由此可见,使用状态模式的好处很明显,可以使得每种状态和它所对应的行为之间的关系局部化,当light需要增添一个新的状态的时候,无需编写过多的if-else语句,只需要新增一个状态类,再稍稍更改现有的代码即可。
同样也可以看出状态模式的缺点,其缺点在于会使得系统中增添很多的状态类。因此系统会增减不少对象,同事由于避开了if-else语句,也造成了逻辑分散的问题,无法再一个地方看出整个状态机的逻辑。
其实,上面的例子里,再javascript这种无类的语言中,没有规定让状态对象一定要从某个类中创建出来,通过Function.prototype.call方法可以直接将请求委托给某个字面量兑现来执行。
- var Light=function(){
- this.button=null;
- this.lightSpan=document.getElementById('lightState');
- this.curState=FSM.off;
- };
- var FSM={
- 'on':{
- 'buttonWasPressed':function(){
- this.curState=FSM.off;
- this.lightSpan.innerHTML='关闭';
- }
- },
- 'off':{
- 'buttonWasPressed':function(){
- this.curState=FSM.on;
- this.lightSpan.innerHTML='打开';
- }
- }
- };
- Light.prototype.init=function(){
- var button=document.createElement('button');
- button.innerHTML='开关';
- var self=this;
- this.button=document.body.appendChild(button);
- button.onclick=function(){
- self.curState.buttonWasPressed.call(self);
- };
- };
- var light=new Light();
- light.init();
javascript设计模式学习之十六——状态模式的更多相关文章
- C#设计模式学习笔记:(18)状态模式
本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/8032683.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第六个模式--状 ...
- Javascript设计模式理论与实战:状态模式
在软件开发中,很大部分时候就是操作数据,而不同数据下展示的结果我们将其抽象出来称为状态,我们平时开发时本质上就是对应用程序的各种状态进行切换并作出相应处理.状态模式就是一种适合多种状态场景下的设计模式 ...
- 前端读者 | Javascript设计模式理论与实战:状态模式
本文来自 @狼狼的蓝胖子:链接:http://luopq.com/2015/11/25/design-pattern-state/ 在软件开发中,很大部分时候就是操作数据,而不同数据下展示的结果我们将 ...
- javascript设计模式学习之十二——享元模式
一.享元模式的定义及使用场景 享元模式是为了解决性能问题而诞生的设计模式,这和大部分设计模式为了提高程序复用性的原因不太一样,如果系统中因为创建了大量类似对象而导致内存占用过高,享元模式就非常有用了. ...
- javascript设计模式学习之十——组合模式
一.组合模式定义及使用场景 组合模式将对象组合成树形结构,用以表示“部分—整体”的层次结构,除了用来表示树形结构之外,组合模式还可以利用对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性. ...
- javascript设计模式学习之十五——装饰者模式
一.装饰者模式定义 装饰者模式可以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象.这种为对象动态添加职责的方式就称为装饰者模式.装饰者对象和它所装饰的对象拥有一致的接口,对于用 ...
- javascript设计模式学习之十四——中介者模式
一.中介者模式的定义和应用场景 中介者模式的作用在于解除对象之间的紧耦合关系,增加一个中介者之后,所有对象都通过中介者来通信,而不是互相引用,当一个对象发生变化的时候,仅需要通知中介者即可.从而将网状 ...
- javascript设计模式学习之十三——职责链模式
一.职责链的定义和使用场景 职责链模式的定义是,职责链模式将一系列可能会处理请求的对象连接成一条链,请求在这些对象之间一次传递,直到遇到一个可以处理它的对象.从而避免请求的发送者和接收者之间的耦合关系 ...
- JavaScript设计模式学习——builder pattern(建造者模式)
个人理解的应用场景 举个例子,比如想要创建各种类型的车的实例,车的类型有很多种,但创建每种类型车的接口定义可能是一样的,就用到了此模式 相关概念的通俗解释 上述例子中接口的定义叫builder 接口到 ...
随机推荐
- autoLayout 纯代码
SB中拖好空间,让后分别在,Pin,Align,Resolve Auto Layout Issues三个面板中设置好约束就好了. 用存代码的方式给控件添加约束,完成自动布局: 利用NSLayoutCo ...
- 强、软、弱、虚引用,ReferenceQueue,WeakHashMap
强引用(Reference):所谓强引用就是普通引用.普通引用引用的对象,即使内存不足时,一般情况下也不会被回收. 软引用(weakReference):如果对象被且仅被软引用所引用时,内存不足时,会 ...
- FZU 2092 bfs+记忆化搜索
晚上团队训练赛的题 和普通bfs不同的是 这是同时操纵人与影子两个单位进行的bfs 由于可能发生人和影子同时接触水晶 所以不可以分开操作 当时使用node记录人和影子的位置 然后进行两重for循环来分 ...
- 一个项目软件的大小基本都占用在外部引用的jar包上了。
1.一个项目几百兆,基本都是外部jar包,引用的. 2.自己本身业务代码并没有那么多的 3.看下meven的仓库大小就知道了,都几百兆
- centos时间同步方法
centos时间同步方法 电脑软硬件应用网 45IT.COM 时间:2012-12-08 18:09 作者:李本清 新装的服务器可能设置了错误的,需要调整时区并调整时间.如下是使用NTP来从一个时间服 ...
- 微信自定义菜单说php json_encode不转义中文汉字的方法
http://blog.csdn.net/qmhball/article/details/45690017 最近在开发微信自定义菜单. 接口比较简单,就是按微信要求的格式post一段json数据过去就 ...
- Natural Language Toolkit
http://www.nltk.org/ >>> import nltk >>> nltk.download()
- Java中同步
解决资源共享的同步操作,有两种方法:一是同步代码块,二是同步方法. 在需要同步的代码块加上synchronized关键字, 同步代码块时必须指定一个需要同步的对象,但一般都是将当前对象(this)设置 ...
- oracle 嵌套表
--自定义对象 CREATE OR REPLACE TYPE Fas_checksheetinfo_line_obj AS OBJECT( CSID_ID VARCHAR2(32 ...
- Xcode Shortcuts
Description:⌘: Command ⌥: Option ⌃: Control ←↑↓→: Left, Up, Down, Right ...