设计模式 | 状态模式 | 代码详解 js 版本

896 阅读4分钟

这是我参与8月更文挑战的第 28 天,活动详情查看:8月更文挑战

设计模式,是前人总结的一些经典场景下的经典的解决方案。核心其实是一种思想,无关乎语言。

这个讲解的用例已经到位了,就不贴我的项目里 js 实例代码了

何为状态模式

在我们的程序中,经常会有这么一种情况,我们需要根据外部传入的状态,来选择该怎么做,而这种选择,会散落在代码的各个地方,并且,每次选择时做的事情的类型不一定相同,即无法直接为外部的一个公共函数

代码演示

经典场景复现

function out(type){
    // ...
    switch (type){
      case 1:
        // do first like this
        break;
      case 2:
        // do first like that
        break;
      // ...
    }
    // .....
    switch (type){
      case 1:
        // do second like this
        break;
      case 2:
        // do second like that
        break;
      // ...
    }
    // ......
}

因为是涉及到状态,每次处理的时候,都需要使用上 switch case 或者是 if else if,而且一个条件选择后的实际处理函数,肯定不会是 do xxx 这种轻飘飘的一行,这些代码是嵌套在我们正常的一个业务处理当中,会使外层的函数变得非常膨胀,当然,也能封装成函数调用,只是,管理和维护是一个大的问题

代码优化 v1

或许,我们可以将 case 中的代码抽出来,使用函数命名进行约定,效果类似于

function() doFirst_1(){}
function() doFirst_2(){}
// ....

再次优化 v2

我们也可以用闭包的思路来改进上一步的代码,将相关状态的代码整合到一起

let doThings = function(){
  return {
    type_1: {
      first(){},
      second(){},
    },
    type_2: {
      first(){},
      second(){},
    },
    // 。。。
  }
}();

然后,我们在 case 中用类似于 doThings.type_1.first() 的方式对方法进行调用

反思 v1-1

或者,上面的这种优化不对,我们直接将整个 switch 打包出来?

function first(type){
  switch (type){
    case 1:
      // do first like this
      break;
    	// ...
  }
}

丑陋的代码不过是藏在了另一个地方罢了

套用设计模式 v3

先说结论,这种情况就和状态模式非常的契合,我们按照状态去维护对应的方法

v2 已经有这种雏形了,但是没有处理好 switch,那我们直接将状态的选择封装到这个函数的内部,并提供方法来支持状态切换

思路:

  1. 为每一个状态定义一个 object,在这个 object 中按照状态去实现对应的方法
  2. 维护一个状态映射到第一步定义的 object 上,因为有的状态定义不一定是 0, 1, 2...
  3. 定义一个内部的状态变量
  4. 声明一个方法,支持外部来变更这个状态
  5. 暴露一个实例,让外部直接调用,这个实例对应外部指定的状态
let doThings = function(){
  let type_1 = {	// (1)
      first(){},
      second(){},
    }, type_2 = {
      first(){},
      second(){},
    };
  let status = {	// (2)
    0: type_1,
    1: type_2,
  }
  let innerType = 0;	// (3)
  return {
    setType(type){	// (4)
      innerType = type;
    },
    instance : status[innerType],	// (5)
    }
  }
}();

调用这个状态方法的外部的函数,业务显得非常的单纯,切换当前状态,然后直接进行方法调用,代码结构就显得非常的简洁

function out(type = 0){
  doThings.setType(type);
  doThings.instance.first();
  // 做一些其他的业务
  doThings.instance.second();
  // ...
}

我觉得这个是我总结的一个适用性较高的代码模版,工作中如果确实能够用上,直接照着这个模版进行编码就妥了

优点

v3 的代码中,如果我们需要扩展新的状态,需要进行如下 2 步操作

  • (1) 的位置,添加一个新的状态实例,并去实现对应的操作方法
  • (2) 的位置,建立新的映射关系

缺点

按原先的编码方式,如果我们需要为现在的状态,扩展新的操作方法 doThree1(), doThree2(),我们直接在对应的地方直接扩展一套 switch + func() 定义,这个操作非常直接

换成我们改造的模版模式,我们需要在 v3 的 (1) 位置,在每一个状态实例上添加一个对应的 three() 的实现,改造起来相对于旧编码方式应该会麻烦一点点,吧。。

总结

好的编码,应该是便于阅读和扩展,这种设计的优点体现在后续的迭代升级上,虽然老板眼里只看到程序能不能跑,要是这玩意大概率轮到自己维护的情况,给以后的自己留一点摸鱼的时间不香嘛

图片.png