各位写代码的时候,经常会出现条件判断吧,那么条件判断里面脸最熟的当属 if 了吧,这个东西在我们开始编码的时候,真的是隔一段时间不写,就浑身难受。
??但是呢,if 这个东西,哪怕是再简单的判断,也会有隐含问题,而且在代码可读性上,条件判断的效果很不友好,当存在多重判断的时候,简直就是灾难。
??所以为了解决条件判断的这些弊端,状态模式应运而生。
状态模式
??状态模式:当一个对象的内部状态发生改变时,会导致其行为的改变,这看起来像是改变了对象。
??我们可以将不同的状态结果封装在状态对象内部,然后该状态对象返回一个可被调用的接口方法,用于调用状态对下内部某种方法。做法如下:
//状态对象
var ResultState = function() {<!-- -->
//结果保存在内部状态中
var States = {<!-- -->
//每种状态作为一种独立方法保存
state0:funciton() {<!-- -->
//处理结果0
console.log('这是第一种情况');
},
state1:funciton() {<!-- -->
//处理结果1
console.log('这是第二种情况');
},
state2:funciton() {<!-- -->
//处理结果2
console.log('这是第三种情况');
},
state3:funciton() {<!-- -->
//处理结果3
console.log('这是第四种情况');
},
}
//获取某一种状态,并执行其方法
function show(result) {<!-- -->
States['state' + result] && States['state' + result]();
}
return {<!-- -->
//返回调用状态方法接口
show: show
}
} ();
当我们想调用第三种结果的时候,我们就可以按照下面这种方法实现。
//展示结果3
ResultState.show(3);
上面方法展示了状态模式的基本雏形,该模式主要目的就是将条件判断的不同结果转化为状态对象的内部状态。由于是内部状态,所以创建时设置成私有变量,只提供一个接口给外部进行增删改查,方便了我们对状态对象中,内部对象的管理。
??大家小时候都玩过超级玛丽吧,我们操作人物的时候可以跳跃,奔跑,蹲下,丢飞镖等,这些都是一个一个的状态,如果我们用 if 或者 switch 条件判断的话,那么后期会相当难维护,因为增加或者删除一个状态需要修改的地方太多了,这时使用状态模式帮我们管理就再合适不过了。
??对于超级玛丽,有的时候需要跳起来丢飞镖,有的时候需要蹲下丢飞镖,有的时候需要跑起来丢,这些组合状态如果用 if 或者 switch 判断的话,无形当中增加的成本是无法想象的。例如:
//单动作条件判断 每增加一个动作就需要添加一个判断
var lastAction = '';
function changeMarry(action) {<!-- -->
if(action = 'jump') {<!-- -->
//跳跃动作
} else if(action = 'move') {<!-- -->
//移动动作
} else {<!-- -->
//默认情况
}
lastAction = action;
}
//符合动作对条件判断的开销是翻倍的
var lastAction1 = '';
var lastAction2 = '';
function changeMarry(action1, action2) {<!-- -->
if(action1 = 'throw') {<!-- -->
//丢飞镖
} else if(action1 = 'jump') {<!-- -->
//跳跃
} else if(action1 = 'jump' && action2 = 'throw') {<!-- -->
//跳起来扔飞镖
} else if(action1 = 'move' && action2 = 'throw') {<!-- -->
//移动中扔飞镖
}
//保留上一个动作
lastAction1 = action1 || '';
lastAction2 = action2 || '';
}
上面代码的可维护性和可读性都是很差的,日后对于新动作的添加或者修改原有动作的成本都是很大的。那么为了解决这一问题我们引入状态模式。解决思路如下:
1.创建一个状态对象。
2.内部保存状态变量。
3.内部封装好每种动作对应的状态。
4.状态对象返回一个接口对象,可以对内部状态进行修改或者调用。
//创建超级玛丽状态类
var MarryState = function() {<!-- -->
//内部状态私有变量
var _currentState = {<!-- -->},
//动作与状态方法映射
states = {<!-- -->
jump: function() {<!-- -->
//跳跃
console.log('jump');
},
move: function() {<!-- -->
//移动
console.log('move');
},
throw: function() {<!-- -->
//丢飞镖
console.log('throw');
},
squat: function() {<!-- -->
//蹲下
console.log('squat');
}
};
//动作控制类
var Action = {<!-- -->
//改变状态方法
changeState: function() {<!-- -->
//组合动作通过传递多个参数实现
var arg = arguments;
//重置内部状态
_currentState = {<!-- -->};
//如果有动作则添加动作
if(arg.length) {<!-- -->
//遍历动作
for(var i = 0; len = arg.length; i < len; i++) {<!-- -->
//向内部状态中添加动作
_currentState[arg[i]] = true;
}
}
//返回动作控制类
return this;
},
//执行动作
goes: function() {<!-- -->
console.log('触发一次动作');
//遍历内部状态保存的动作
for(var i in currentState) {<!-- -->
//如果该动作存在则执行
states[i] && states[i]();
}
return this;
}
}
//返回接口方法change ,goes
return {<!-- -->
change: Action.changeState,
goes: Action.goes
}
}
接下来就是使用了。
//创建一个超级玛丽
var marry = new MarryState();
marry
.change('jump', 'throw') //添加跳跃与丢飞镖动作
.goes() //执行动作
.goes() //执行动作
.change('throw') //添加丢飞镖动作
.goes(); //执行动作
输出结果如下:
//触发一次动作
//jump
//throw
//触发一次动作
//jump
//throw
//触发一次动作
//throw
基于ES6 Map 函数优化对象
states1 = new Map([
[1, () => {// do sth1}],
[2, () => {// do sth2}],
[3, () => {// do sth3}],
[4, () => {// do sth4}],
[5, () => {// do sth5}],
['default', () => {// do sth6}]
])
states2 = new Map([
['A_1', () => {// do sth1}],
['A_2', () => {// do sth2}],
['B_1', () => {// do sth3}],
['B_2', () => {// do sth4}],
// ...
['default', () => {// do sth5}]
])
但如果状态规模足够大,或者键值之间的关系更加复杂多变时,map中穷举这样的实现方式也和if...else
分支一样繁琐。先了解一下map与object的巨大差异。如MDN:
- Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值
– Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map 对象是按插入的顺序返回键值。 - 你可以通过
size
属性直接获取一个Map
的键值对个数,而Object
的键值对个数只能手动计算。 Map
可直接进行迭代,而Object
的迭代需要先获取它的键数组(当然你可以用Object.keys().length来计算),然后再进行迭代。Object
都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。虽然 ES5 开始可以用map = Object.create(null)
来创建一个没有原型的对象,但是这种用法不太常见。Map
在涉及频繁增删键值对的场景下会有些性能优势。
其中特性是map键可以为正则;正则的灵活性可以充分简化绝大部分复杂多变的状态关系
states3 = new Map([
[/^A_[1-3]$/, () => {// do sth1}],
[/^A_4$/, () => {// do sth1}],
// ...
[/^A_.*$/, () => {// do common thing}]
])
这些例子足以在今后处理复杂分支问题上,带给你诸多灵感。
转自:转自