• 《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
  • 最新文章
  • 热门文章
一二三 - 开发者的网上家园