《javascript设计模式与开发实践》阅读笔记(16)—— 状态模式

状态模式

会区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。比如电灯的开关是开还是关,在外界的表现就完全不同。

电灯例子

按照常规思路,实现一个电灯就是构造一个电灯类,然后指定一下它的开关是什么,每次开关改变,触发电灯相应的方法。

 1     var Light = function(){
 2         this.state = 'off'; // 给电灯设置初始状态off
 3         this.button = null; // 尚未指定按钮
 4     };
 5 
 6     Light.prototype.init = function(){  //初始化,创造一个按钮给电灯对象
 7         var button = document.createElement( 'button' ),
 8         self = this;  //保存引用
 9         button.innerHTML = '开关';
10         this.button = document.body.appendChild( button );
11         this.button.onclick = function(){  //点一次开关调用一次对象的change方法
12             self.change(); 
13         }
14     };
15 
16     Light.prototype.change = function(){
17         if ( this.state === 'off' ){
18             console.log( '开灯' );
19             this.state = 'on';
20         }else if ( this.state === 'on' ){
21             console.log( '关灯' );
22             this.state = 'off';
23         }
24     };
25 
26     var light = new Light();
27     light.init();

这段代码是非常常规的实现,逻辑上也很容易理解,但有个小问题,所有的状态是写死在change方法里的,倘若我们想要添加一些状态就得深入进去修改,而且,想要知道对象一共有多少状态也得进去一个一个数,在代码量很多的情况下,change方法也会变得很臃肿。

引入模式方法前的思考

如果想要依次改变状态,而且可以知道一共有多少种状态,感觉通过数组完全可以实现,在light属性里定义一个状态数组,light的当前状态就是数组的第一个元素,每当点击时把状态改成数组的下一位。而且通过获取数组长度也很容易知道一共有多少状态,且能全部打印出来,修改起来也很方便。

 1     var Light = function(){
 2         this.stateArr=["off","on","small_light"];  //三个状态,关闭,点亮,暗一点
 3         this.state = this.stateArr[0]; // 给电灯设置初始状态off
 4         this.button = null; // 尚未指定按钮
 5     };
 6     
 7     Light.prototype.change=function(){
 8 
 9         var num=this.stateArr.indexOf(this.state);
10         num=(num==this.stateArr.length-1)?0:num+1;
11         this.state=this.stateArr[num];
12         console.log( this.state );        
13     }

改动之后每次点击依次触发 "on" "small_light" "off"。

不过现在还有一个问题,我们只是做到了可以依次改变状态和随便新增状态,但是change函数本质上也仅仅可以遍历状态而已,需要每种状态执行不同的函数时,现在的代码还是无能为力。我们希望的是,change函数遍历到对的状态,有一句通用的代码可以执行正确的函数。

所以,仅仅是把状态变成数组是不够的,我们需要的是一个对象,里面不但存储了状态,还应该存储了相应方法。我们统一这些方法的名称,在change函数里就可以统一调用。

状态模式实现

 1     /****新建off类***/
 2     var off=function(){
 3         this.statename="off";
 4     }
 5     off.prototype.do=function(){
 6         console.log("关灯啦");
 7     }
 8 
 9     /****新建on类****/
10     var on=function(){
11         this.statename="on";
12     }
13     on.prototype.do=function(){
14         console.log("开灯啦");
15     }
16 
17     /****新建small_light类****/
18     var small_light=function(){
19         this.statename="small_light";
20     }
21     small_light.prototype.do=function(){
22         console.log("光线变暗啦");
23     }
24 
25     var Light = function(){
26         this.stateArr=[];  //存储状态对象
27         this.stateArr.push(new off());    //添加状态对象,如果要增删或者调整顺序,通过操作数组或者这里手动修改都可以做到
28         this.stateArr.push(new on());
29         this.stateArr.push(new small_light());
30 
31         this.stateobj=this.stateArr[0];    //指定初始状态对象
32         this.state = this.stateobj.statename; // 给电灯设置初始状态
33         this.button = null;   // 尚未指定按钮
34     };
35 
36     Light.prototype.init = function(){  //初始化,创造一个按钮给电灯对象
37         var button = document.createElement( 'button' ),
38         self = this;
39         button.innerHTML = '开关';
40         this.button = document.body.appendChild( button );
41         this.button.onclick = function(){  //点一次开关调用一次对象的change方法
42             self.change(); 
43         }
44     };
45 
46     Light.prototype.change = function(){
47         var num=this.stateArr.indexOf(this.stateobj);  //返回当前状态对象在数组的索引
48         num=(num==this.stateArr.length-1)?0:num+1;     //根据索引做些处理
49 
50         this.stateobj=this.stateArr[num];    //重新指定状态对象
51         this.state=this.stateobj.statename;  //改变状态
52         this.stateobj.do();     //执行新状态对象的代码
53     };
54 
55     var light = new Light();
56     light.init();
57 
58     /****点击按钮执行结果***/
59     // 开灯啦
60     // 光线变暗啦
61     // 关灯啦
62     // 开灯啦
63     //....(不断重复)

上述代码可以自行去浏览器的控制台测试,这里还有一点小问题,就是接口的统一(do方法)完全要靠程序员的自觉和记忆力,有的时候,如果在代码较多的时候,因为没有接口统一引起bug,调试起来很麻烦,所以我们想到了模板方法中为了避免这种情况的做法。让所有状态类都产生于一个抽象类,抽象类的do方法中抛出一个错误,如果状态类没有重写该方法,在程序执行后也能很快发现错误的原因。

状态模式的优缺点

优点
  状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。

  避免Context(上下文)无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了Context中原本过多的条件分支。
缺点
  是会在系统中定义许多状态类,编写多个状态类是一项枯燥乏味的工作,而且系统中会因此而增加不少对象。另外,由于逻辑分散在状态类中,虽然避开了不受欢迎的条件分支语句,但也造成了逻辑分散的问题,我们无法在一个地方就看出整个状态机的逻辑。

 

总结

这里因为例子如此,所以用了数组来管理,而实际的开发中,会出现各种各样切换状态的方式,不一定是顺序的;而且可能存在两个或者更多按钮,他们的点击都会导致状态变化,这种时候,就需要根据具体的情况,完成状态的切换和执行相应代码。

原文地址:https://www.cnblogs.com/grey-zhou/p/6379062.html