状态模式
什么是状态模式? 状态模式 (State Pattern)允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类,类的行为随着它的状态改变而改变。
状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
当程序需要根据不同的外部情况来做出不同操作时,最直接的方法就是使用 switch-case 或 if-else 语句将这些可能发生的情况全部兼顾到,但是这种做法应付复杂一点的状态判断时就有点力不从心,开发者得找到合适的位置添加或修改代码,这个过程很容易出错,这时引入状态模式可以某种程度上缓解这个问题。
在等红绿灯的时候,红绿灯的状态和行人汽车的通行逻辑是有关联的: 1红灯亮:行人通行,车辆等待; 2绿灯亮:行人等待,车辆通行; 3黄灯亮:行人等待,车辆等待
还有下载文件时,有好几个状态,比如下载验证、下载中、暂停下载、下载完毕、下载失败,文件在不同状态下表现的行为也不一样,比如下载中时显示可以暂停下载和下载进度,下载失败时弹框提示并询问是否重新下载等等。类似的场景还有很多,比如女生作为你的朋友、好朋友、女朋友、老婆等不同状态的时候,行为也不同 。
在这些场景中,有以下特点: 1、对象有有限多个状态,且状态间可以相互切换; 2、各个状态和对象的行为逻辑有比较强的对应关系,即在不同状态时,对应的处理逻辑不一样;
状态模式的实现 下面用最常用的方式来实现上面的红绿灯的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 let trafficLight = (function ( ) { let state = '绿灯' return { setState : function (target ) { if (target === '红灯' ) { state = '红灯' console .log('交通灯颜色变为 红色,行人通行 & 车辆等待' ) } else if (target === '黄灯' ) { state = '黄灯' console .log('交通灯颜色变为 黄色,行人等待 & 车辆等待' ) } else if (target === '绿灯' ) { state = '绿灯' console .log('交通灯颜色变为 绿色,行人等待 & 车辆通行' ) } else { console .error('交通灯还有这颜色?' ) } }, getState : function ( ) { return state } } })() trafficLight.setState('红灯' ) trafficLight.setState('黄灯' ) trafficLight.setState('绿灯' ) trafficLight.setState('紫灯' )
这里可以使用if-else来实现,也可以使用swich-case来实现,但是这样实现是存在问题的,这里处理的逻辑比较简单,如果比较复杂,在增加新的状态时,比如增加了 蓝灯、紫灯 等颜色及其处理逻辑的时候,需要到 setState 方法里找到对应地方修改。
在实际项目中,if-else 伴随的业务逻辑处理通常比较复杂,找到要修改的状态就不容易,特别是如果是别人的代码,或者接手遗留项目时,需要看完这个 if-else 的分支处理逻辑,新增或修改分支逻辑的过程中也很容易引入 Bug。
正式因为这样非常的不方便维护状态及其对应的行为,所以引入了状态模式的理念,状态模式把每种状态和对应的处理逻辑封装在一起,比如下面用一个类实例将红绿灯的逻辑封装起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 var AbstractState = function ( ) {}AbstractState.prototype.employ = function ( ) { throw new Error ('抽象方法不能调用!' ) } var State = function (name, desc ) { this .color = { name, desc } } State.prototype = new AbstractState() State.prototype.employ = function (trafficLight ) { console .log('交通灯颜色变为 ' + this .color.name + ',' + this .color.desc) trafficLight.setState(this ) } var TrafficLight = function ( ) { this .state = null } TrafficLight.prototype.getState = function ( ) { return this .state } TrafficLight.prototype.setState = function (state ) { this .state = state } var trafficLight = new TrafficLight()var redState = new State('红色' , '行人等待 & 车辆等待' )var greenState = new State('绿色' , '行人等待 & 车辆通行' )var yellowState = new State('黄色' , '行人等待 & 车辆等待' )redState.employ(trafficLight) yellowState.employ(trafficLight) greenState.employ(trafficLight)
这里的不同状态是同一个类的类实例,比如 redState 这个类实例,就把所有红灯状态处理的逻辑封装起来,如果要把状态切换为红灯状态,那么只需要 redState.employ() 把交通灯的状态切换为红色,并且把交通灯对应的行为逻辑也切换为红灯状态。
下面使用 ES6 的 Class 语法对上面的代码进行改造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class AbstractState { constructor ( ) { if (new .target === AbstractState) { throw new Error ('抽象类不能直接实例化!' ) } } employ ( ) { throw new Error ('抽象方法不能调用!' ) } } class State extends AbstractState { constructor (name, desc ) { super () this .color = { name, desc } } employ (trafficLight ) { console .log('交通灯颜色变为 ' + this .color.name + ',' + this .color.desc) trafficLight.setState(this ) } } class TrafficLight { constructor ( ) { this .state = null } getState ( ) { return this .state } setState (state ) { this .state = state } } const trafficLight = new TrafficLight()const redState = new State('红色' , '行人等待 & 车辆等待' )const greenState = new State('绿色' , '行人等待 & 车辆通行' )const yellowState = new State('黄色' , '行人等待 & 车辆等待' )redState.employ(trafficLight) yellowState.employ(trafficLight) greenState.employ(trafficLight)
如果要新建状态,不用修改原有代码,只要加上下面的代码:
1 2 const blueState = new State('蓝色' , '行人倒立 & 车辆飞起' )blueState.employ(trafficLight)
传统的状态区分一般是基于状态类扩展的不同状态类,如何实现实现看需求具体了,比如逻辑比较复杂,通过新建状态实例的方法已经不能满足需求,那么可以使用状态类的方式。
最后,提供一个状态类的实现,同时引入状态的切换逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 this .colorState = '红色' } employ ( ) { console .log('交通灯颜色变为 ' + this .colorState + ',行人通行 & 车辆等待' ) } changeState (trafficLight ) { trafficLight.setState(trafficLight.yellowState) } } class GreenState extends AbstractState { constructor ( ) { super () this .colorState = '绿色' } employ ( ) { console .log('交通灯颜色变为 ' + this .colorState + ',行人等待 & 车辆通行' ) } changeState (trafficLight ) { trafficLight.setState(trafficLight.redState) } } class YellowState extends AbstractState { constructor ( ) { super () this .colorState = '黄色' } employ ( ) { console .log('交通灯颜色变为 ' + this .colorState + ',行人等待 & 车辆等待' ) } changeState (trafficLight ) { trafficLight.setState(trafficLight.greenState) } } class TrafficLight { constructor ( ) { this .redState = new RedState() this .greenState = new GreenState() this .yellowState = new YellowState() this .state = this .greenState } setState (state ) { state.employ(this ) this .state = state } changeState ( ) { this .state.changeState(this ) } } const trafficLight = new TrafficLight()trafficLight.changeState() trafficLight.changeState() trafficLight.changeState()
如果要增加新的交通灯颜色,也是很方便的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class BlueState extends AbstractState { constructor ( ) { super () this .colorState = '蓝色' } employ ( ) { console .log('交通灯颜色变为 ' + this .colorState + ',行人倒立 & 车辆飞起' ) const redDom = document .getElementById('color-blue' ) redDom.click() } } const blueState = new BlueState()trafficLight.employ(blueState)
状态管理的原理
所谓对象的状态,通常指的就是对象实例的属性的值。行为指的就是对象的功能,行为大多可以对应到方法上。状态模式把状态和状态对应的行为从原来的大杂烩代码中分离出来,把每个状态所对应的功能处理封装起来,这样选择不同状态的时候,其实就是在选择不同的状态处理类。
也就是说,状态和行为是相关联的,它们的关系可以描述总结成:状态决定行为。由于状态是在运行期被改变的,因此行为也会在运行期根据状态的改变而改变,看起来,同一个对象,在不同的运行时刻,行为是不一样的,就像是类被修改了一样。
为了提取不同的状态类共同的外观,可以给状态类定义一个共同的状态接口或抽象类,正如之前最后的两个代码示例一样,这样可以面向统一的接口编程,无须关心具体的状态类实现。
状态模式的优缺点
状态模式的优点:
●结构相比之下清晰,避免了过多的 switch-case 或 if-else 语句的使用,避免了程序的复杂性提高系统的可维护性;
●符合开闭原则,每个状态都是一个子类,增加状态只需增加新的状态类即可,修改状态也只需修改对应状态类就可以了;
●封装性良好,状态的切换在类的内部实现,外部的调用无需知道类内部如何实现状态和行为的变换。
状态模式的缺点:引入了多余的类,每个状态都有对应的类,导致系统中类的个数增加。
状态模式的应用场景
在以下场景中可以使用状态模式:
●操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态,那么可以使用状态模式来将分支的处理分散到单独的状态类中;
●对象的行为随着状态的改变而改变,那么可以考虑状态模式,来把状态和行为分离,虽然分离了,但是状态和行为是对应的,再通过改变状态调用状态对应的行为;