状态模式通识篇

377 阅读4分钟
原文链接: damobing.com

前言

状态模式也是行为型模式中的一种,顾名思义状态模式主要是基于对象有不同的状态,从而导致具有与其对应状态的行为。

场景

为了更好的理解状态模式,我们假设有这样的需要,我们有一个电灯,电灯可以在有打开、关闭、坏的三种情况。在开灯的时候,我们可以看书;在关灯的时候,我们什么也看不到;在灯坏的时候,我们不能进行任何操作。想一想你的代码会如何写呢?

// 一如既往的零散代码 获取电灯状态
let status = light.status
switch(status){
    case 'on':console.log('light is on ,we can read books');
    break;
    case 'off':console.log('light is off ,we can not see anything');
    break;
     case 'error':console.log('light is error ,we can not do any operation');
    break;
}

但是你想一直这样写么?这个代码是只执行一次的,为了更好的进行函数的执行,我们也许会把状态判断封装为一个函数,比如下面这样。

function lightTip(status){
    case 'on':console.log('light is on ,we can read books');
    break;
    case 'off':console.log('light is off ,we can not see anything');
    break;
     case 'error':console.log('light is error ,we can not do any operation');
    break;
}
// 获取状态执行操作
let status = light.status 
lightTip(status)
// 其他逻辑里更改状态执行
light.status = 'off'
// 继续执行
let status = light.status 
lightTip(status)

思考抽象为电灯状态

等等,当这样写下去,其实明明是很规整特殊的一个电灯对象,为什么不按照灯这个类封装一下,然后根据不同状态提供行为,同时把设置状态的部分,每个状态执行操作的部分都封装一下,方便维护也方便更加规范的切换。不妨看下下面这种写法??(图是java的,js的原理是一样的,理解就好)

image

codepen之状态模式设计案例

// 电灯类
class light{
  setState(state){
    this.state = state
  }
  getState(){
      return this.state.status
  }
  request(){
    return this.state.handler()
  }
} 
// 状态类
class lightState{
  constructor(){
    this.status = ''
  }
  handler(context){
    console.error('不在任何有效状态')
  }
}
// 具体状态实现 启动状态
class onState extends lightState{
  constructor(){
    super();
    this.status='on'
  }
  handler(){
    console.log('light is on ,we can read books')
  }
}
// 关闭状态实现
class offState extends lightState{
  constructor(){
    super();
    this.status='off'
  }
  handler(){
    console.log('light is off ,we can not see anything')
  }
}

let lightDemo = new light()
lightDemo.setState(new onState())
lightDemo.request()

lightDemo.setState(new offState())
lightDemo.request()

这样设计区别之后,我们每个电灯都可以分别的管理,每个电灯的状态都继承自状态类,需要实现其必要的抽象方法以及在这个公共方法里执行需要的一些状态里的方法。

说好的玛丽呢

其实状态模式不一定要class类来具体实现,它更是一种设计思想,只要你是按照这样的思想去组织代码就可以了。

先说下基本的需求,我们按照游戏设定,给其提供不同的动作,包括眺,前进,射击,蹲下以及复合的一些操作,我们需要根据用户需要可以追加一系列的操作,追加之后可以执行,也可以随时追踪到玛丽最新的动作状态。

同样我会这短代码写到codepen上,方便我们看到最终设计之后的一个效果。

codepen超级玛丽案例

function MarryState(){
// 存储用户输入的状态
  var currentState = {}
  let marry = document.getElementById('marry');
  // 针对每个状态 定义对应状态需要写的代码
  var state = {
     forward:function(){
       let marginLeft = marry.style.marginLeft
       marry.style.marginLeft = !marginLeft? '15px': parseInt(marginLeft)+15+'px'
     },
    back:function(){
       let marginLeft = marry.style.marginLeft
       marry.style.marginLeft = !marginLeft? '15px': parseInt(marginLeft)-15+'px'
     },
    jump:function(){
       marry.classList.add('jump')  ;   setTimeout(function(){marry.classList.remove('jump')},1000)
     },
  }
  // 每个action的部分 提供链式操作,所以每次执行完之后返回this
  var action = {
    changeState:function(){
      var areg = arguments ;
      currentState={};
      if(areg.length){
        for(var i=0,len=areg.length;i<len;i++){
          currentState[areg[i]] = true;
        }
      }
      return this;
    },
    go:function(){
      for(var p in currentState){
        state[p] && state[p]()
      }
      return this;
    }
  }

  return {
    change:action.changeState,
    go:action.go
  }
}
// 实例化
var marrySample = new MarryState();
// 进行事件委托
let topOpt = document.getElementById("topOpt")
topOpt.addEventListener('click',function(e){
  marrySample.change(e.target.id).go()
 })
// 定义一系列的状态动作
function animateActions(){
  marrySample.change('jump','forward','forward','back').go().go().go().go()
}

animateActions()

小结

分析下,其实以上的代码完全可以通过switch分支实现,之所以封装一个对应状态对象的函数,并通过暴露改变状态的方法以及执行操作的方法执行对应状态的代码逻辑。

所以其真正适用的场景是:
– 代码中包含了大量与状态相关的判断语句
– 对象的行为与状态强相关绑定,并随着状态的改变而改变
– 一个对象的状态并不是锁定的,而是可能会动态变化的,甚至是规律辩护,比如玛丽的跳跃射击的操作、向前跳跃的操作等。

这样的模式设计之后的优点是:
– 减少了因为不同状态判断写的分支语句或者条件判断语句
– 每个状态维护在一个子类或者一个封装的方法里,便于单独维护
– 状态放到了内部,外部不知道状态的执行以及互相关系

这样的缺点:
– 如果状态过多,会产生很多子类,第一种方式的缺点;这种时候其实可以采用封装到函数级别即可;