一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情。
什么是“状态模式”?
状态模式:对象行为是根据状态改变,而改变的。
特点:正是由于内部状态的变化,导致对外的行为发生了变化。例如:相同的方法在不同时刻被调用,行为可能会有差异。
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态类抽象出来。
优缺点
优点:
- 封装了转化规则,对于大量分支语句,可以考虑使用状态类进一步封装。
- 每个状态都是确定的,对象行为是可控的。
缺点:
- 状态模式的实现关键是将事物的状态都封装成单独的类,状态模式的使用必然会增加系统类和对象的个数。 这个类的各种方法就是“此种状态对应的表现行为”。因此,程序开销会增大。
使用场景
1、行为随状态改变而改变的场景。
2、条件、分支语句的代替者。
状态模式实现手电筒关灯、强光、弱光三种状态的切换
const obj = {
weakLight: {
press() {
console.log('打开强光');
this.currState = obj.strongLight;
}
},
strongLight: {
press() {
console.log('关灯');
this.currState = obj.offLight;
}
},
offLight: {
press() {
console.log('打开弱光');
this.currState = obj.weakLight;
}
}
}
const Light = function() {
this.currState = obj.offLight;
}
const btn = (() => {
const btn = document.createElement('button');
btn.innerHTML = 'Press Me';
document.body.appendChild(btn);
return btn;
})()
Light.prototype.handlePress = function() {
let self = this;
btn.addEventListener('click', function() {
self.currState.press.call(self);
}, false)
}
const light = new Light();
light.handlePress();
obj为一个状态对象,保存了三种状态模式的操作及具体实现,当触发对应的操作时,立即将当前状态切换为下一个,实现有限状态机这样一种模式,handlePress就是常说的状态类,它的行为随着状态的改变而改变。 下面是ES6的 简单实现
const obj = {
weakLight: {
press() {
console.log('打开强光');
this.currState = obj.strongLight;
}
},
strongLight: {
press() {
console.log('关灯');
this.currState = obj.offLight;
}
},
offLight: {
press() {
console.log('打开弱光');
this.currState = obj.weakLight;
}
}
}
const btn = (() => {
const btn = document.createElement('button');
btn.innerHTML = 'Press Me';
document.body.appendChild(btn);
return btn;
})()
class Light {
constructor(obj) {
this.currState = obj.offLight;
}
handlePress() {
this.currState.press.call(this);
}
}
const light = new Light(obj);
btn.addEventListener('click', () => {
light.handlePress();
}, false)
也可将状态对象放在类得内部:
class Test {
constructor(state) {
this.state = state;
}
actionLight = {
that: this,
off() {
console.log('打开弱光');
this.that.state = 'weak'
},
weak() {
console.log('打开强光');
this.that.state = 'strong'
},
strong() {
console.log('关灯');
this.that.state = 'off'
}
}
changeState() {
this.actionLight[this.state]();
}
}
let a = new Test('off');
a.changeState(); // 打开弱光
a.changeState(); // 打开强光
a.changeState(); // 关灯
a.changeState(); // 打开弱光
a.changeState(); // 打开强光
a.changeState(); // 关灯
总结
状态模式的优点:
- 结构相比之下清晰,避免了过多的 switch-case 或 if-else 语句的使用,避免了程序的复杂性提高系统的可维护性;
- 符合开闭原则,每个状态都是一个子类,增加状态只需增加新的状态类即可,修改状态也只需修改对应状态类就可以了;
- 封装性良好,状态的切换在类的内部实现,外部的调用无需知道类内部如何实现状态和行为的变换。
状态模式的缺点:
- 引入了多余的类,每个状态都有对应的类,导致系统中类的个数增加。
状态模式的适用场景:
- 操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态,那么可以使用状态模式来将分支的处理分散到单独的状态类中;
- 对象的行为随着状态的改变而改变,那么可以考虑状态模式,来把状态和行为分离,虽然分离了,但是状态和行为是对应的,再通过改变状态调用状态对应的行为;
状态模式与其他设计模式的区别
状态模式和策略模式
状态模式和策略模式在之前的代码就可以看出来,看起来比较类似,他们的区别:
- 状态模式: 重在强调对象内部状态的变化改变对象的行为,状态类之间是平行的,无法相互替换;
- 策略模式: 策略的选择由外部条件决定,策略可以动态的切换,策略之间是平等的,可以相互替换; 状态模式的状态类是平行的,意思是各个状态类封装的状态和对应的行为是相互独立、没有关联的,封装的业务逻辑可能差别很大毫无关联,相互之间不可替换。但是策略模式中的策略是平等的,是同一行为的不同描述或者实现,在同一个行为发生的时候,可以根据外部条件挑选任意一个实现来进行处理。
状态模式和发布-订阅模式
这两个模式都是在状态发生改变的时候触发行为,不过发布-订阅模式的行为是固定的,那就是通知所有的订阅者,而状态模式是根据状态来选择不同的处理逻辑。
- 状态模式: 根据状态来分离行为,当状态发生改变的时候,动态地改变行为;
- 发布-订阅模式: 发布者在消息发生时通知订阅者,具体如何处理则不在乎,或者直接丢给用户自己处理; 这两个模式是可以组合使用的,比如在发布-订阅模式的发布消息部分,当对象的状态发生了改变,触发通知了所有的订阅者后,可以引入状态模式,根据通知过来的状态选择相应的处理。
状态模式和单例模式
之前的示例代码中,状态类每次使用都 new 出来一个状态实例,实际上使用同一个实例即可,因此可以引入单例模式,不同的状态类可以返回的同一个实例。
感谢
谢谢你读完本篇文章,希望对你能有所帮助,如有问题欢迎各位指正。
我是Nicnic,如果觉得写得可以的话,请点个赞吧❤。
写作不易,「点赞」+「在看」+「转发」 谢谢支持❤