《JavaScript设计模式与开发实践》模式篇(13)—— 状态模式

1,281 阅读3分钟

状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。

故事背景

我们来想象这样一个场景:有一个电灯,电灯上面只有一个开关。当电灯开着的时候,此时 按下开关,电灯会切换到关闭状态;再按一次开关,电灯又将被打开。同一个开关按钮,在不同 的状态下,表现出来的行为是不一样的

代码实现(未使用状态模式)

var Light = function(){ 
    this.state = 'off'; // 给电灯设置初始状态 off
    this.button = null;// 电灯开关按钮
};
Light.prototype.init = function(){
    var button = document.createElement( 'button' ),
    self = this;
    button.innerHTML = '开关';
    this.button = document.body.appendChild( button ); 
    this.button.onclick = function(){
        self.buttonWasPressed(); 
    }
};
Light.prototype.buttonWasPressed = function(){ 
    if ( this.state === 'off' ){
        console.log( '开灯' );
        this.state = 'on';
    } else if ( this.state === 'on' ){
        console.log( '关灯' );
        this.state = 'off'; 
    }
};
var light = new Light(); 
light.init();

存在的问题

假如现在电灯的状态多了一种,第一次按下打开弱光,第二次按下打开强光,第三次才是关闭电灯。以上的代码就无法满足这种电灯的情况

分析

状态模式的关键是把事物的 每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,所以 button 被按下的的时候,只需要在上下文中,把这个请求委托给当前的状态对象即可,该状态对象会负责渲染它自身的行为。如下图所示

重构思路

  • 定义 3 个状态类
  • 改写 Light 类,使用状态对象记录当前的状态
  • 提供一个 方法来切换 light 对象的状态
/******************** 定义 3 个状态类 ************************/
// OffLightState:
var OffLightState = function( light ){ 
    this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){ 
    console.log( '弱光' ); // offLightState 对应的行为 
    this.light.setState( this.light.weakLightState );// 切换状态到 weakLightState
};
// WeakLightState:
var WeakLightState = function( light ){ 
    this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function(){ 
    console.log( '强光' ); // weakLightState 对应的行为 
    this.light.setState( this.light.strongLightState ); //切换状态到 strongLightState
};
// StrongLightState:
var StrongLightState = function( light ){ 
    this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function(){
    console.log( '关灯' ); // strongLightState 对应的行为
    this.light.setState( this.light.offLightState ); // 切换状态到 offLightState
};
/******************* 改写 Light 类,使用状态对象记录当前的状态 ******************/
var Light = function(){
    this.offLightState = new OffLightState( this ); 
    this.weakLightState = new WeakLightState( this ); 
    this.strongLightState = new StrongLightState( this ); 
    this.button = null;
};
/******************** 提供一个 方法来切换 light 对象的状态 ************************/
Light.prototype.init = function(){
    var button = document.createElement( 'button' ),
    self = this;
    this.button = document.body.appendChild( button ); 
    this.button.innerHTML = '开关';
   this.currState = this.offLightState;
    this.button.onclick = function(){ 
        self.currState.buttonWasPressed();
    } 
};
Light.prototype.setState = function( newState ){
    this.currState = newState; 
};
var light = new Light(); 
light.init();

改进效果

执行结果跟之前的代码一致,但是使用状态模式的好处很明显,它可以使每 一种状态和它对应的行为之间的关系局部化,这些行为被分散和封装在各自对应的状态类之中, 便于阅读和管理代码。 另外,状态之间的切换都被分布在状态类内部,这使得我们无需编写过多的 if、else 条件 分支语言来控制状态之间的转换。

总结

  • 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态 类,很容易增加新的状态和转换。
  • 避免 Context 无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了 Context 中原本过 5 多的条件分支。
  • 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然。
  • Context 中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响

系列文章:

《JavaScript设计模式与开发实践》最全知识点汇总大全