Javascript设计模式之我见:状态模式
大家好!本文介绍状态模式及其在Javascript中的应用。
模式介绍
定义
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
状态模式主要解决的是控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
类图及说明

State:抽象状态
接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换
ConcreState:具体状态
每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理。通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
Context:环境
定义客户端需要的接口,并且负责具体状态的切换。
应用场景
- 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
- 代码中包含大量与对象状态有关的条件语句:一个操作中含有庞大的多分支的条件(if else(或switch case)语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。 State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
优点
- 避免了过多的 swith…case 或者 if..else 语句的使用,避免了程序的复杂性
- 很好的使用体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就增加子类,你要修改状态,你只修改一个子类就可以了
- 封装性非常好,这也是状态模式的基本要求。状态变换放置到了类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。
缺点
- 具体状态类可能会有很多个,不好管理。
状态模式在Javascript中的应用
我的理解
- ConcreteState具体状态类有两个职责:处理本状态必须完成的任务;过渡到其他状态。
- 可以采自下而上的方法来实现状态类,即先实现ConcreteState类,然后再将ConcreteState类的共性提取出来,形成父类State。
类图及说明

User:使用者
使用者具有不同的状态,它创建Context类并将状态的逻辑委托给Context类处理。
示例
小时候大家应该都玩过魂斗罗游戏吧,魂斗罗吃了无敌道具后会变成刀枪不入,吃了火力增强道具后会变成杀人机器。让我们来看看它的状态是如何转换的。
状态图

魂斗罗Warrior有NORMAL、INVINCIBLE、POWER、DEAD四个状态,每个状态都有beNormal、beInvincible、bePower、dead四个方法。有timeOver、getInvincible、getPower、beShotDead四个触发状态的事件。
类图

代码
代码中使用的库:YOOP
Warrior
var Warrior = YYC.Class({
Private: {
_state: null
},
Public: {
//*事件标志
_getInvincible: false,
_getPower: false,
_timeOver: false,
_beShotDead: false,
setState: function (state) {
this._state = state;
},
//*状态方法
beNormal: function () {
switch (this._state) {
case Warrior.NORMAL_STATE:
//本状态beNormal方法的逻辑。已经处于NORMAL状态,不用再转换为NORMAL状态了
console.log("恢复正常");
break;
case Warrior.INVINCIBLE_STATE:
//INVINCIBLE状态下beNormal方法的逻辑
console.log("恢复正常");
//从INVINCIBLE状态转换到NORMAL状态
this.setState(Warrior.NORMAL_STATE);
break;
case Warrior.POWER_STATE:
//POWER状态下beNormal方法的逻辑
console.log("恢复正常");
//从POWER状态转换到NORMAL状态
this.setState(Warrior.NORMAL_STATE);
break;
case Warrior.DEAD_STATE:
//不能起死回生
break;
}
},
beInvincible: function () {
switch (this._state) {
case Warrior.NORMAL_STATE:
console.log("无敌");
this.setState(Warrior.INVINCIBLE_STATE);
break;
case Warrior.INVINCIBLE_STATE:
console.log("无敌");
break;
case Warrior.POWER_STATE:
console.log("无敌");
this.setState(Warrior.INVINCIBLE_STATE);
break;
case Warrior.DEAD_STATE:
break;
}
},
bePower: function () {
switch (this._state) {
case Warrior.NORMAL_STATE:
console.log("火力增强");
this.setState(Warrior.POWER_STATE);
break;
case Warrior.INVINCIBLE_STATE:
console.log("火力增强");
this.setState(Warrior.POWER_STATE);
break;
case Warrior.POWER_STATE:
console.log("火力增强");
break;
case Warrior.DEAD_STATE:
break;
}
},
dead: function () {
switch (this._state) {
case Warrior.NORMAL_STATE:
console.log("死亡");
this.setState(Warrior.DEAD_STATE);
break;
case Warrior.INVINCIBLE_STATE:
//都无敌了当然不会死亡
break;
case Warrior.POWER_STATE:
console.log("死亡");
this.setState(Warrior.DEAD_STATE);
break;
case Warrior.DEAD_STATE:
console.log("死亡");
break;
}
},
action: function () {
//*此处进行触发状态的事件判断
if (this._timeOver) {
this.beNormal();
}
else if (this._getInvincible) {
this.beInvincible();
}
else if (this._getPower) {
this.bePower();
}
else if (this._beShotDead) {
this.dead();
}
}
},
Static: {
NORMAL_STATE: ,
INVINCIBLE_STATE: ,
POWER_STATE: ,
DEAD_STATE:
}
});
场景类
function _resetFlag(warrior) {
warrior._getInvincible = false;
warrior._getPower = false;
warrior._timeOver = false;
warrior._beShotDead = false;
}
function _getInvincible(warrior) {
_resetFlag(warrior);
warrior._getInvincible = true;
}
function _getPower(warrior) {
_resetFlag(warrior);
warrior._getPower = true;
}
function _beShotDead(warrior) {
_resetFlag(warrior);
warrior._beShotDead = true;
}
function main() {
var warrior = new Warrior();
//初始状态为Normal
warrior.setState(Warrior.NORMAL);
//获得无敌道具,进入无敌状态
_getInvincible(warrior);
warrior.action();
//获得火力增强道具,进入火力增强状态
_getPower(warrior);
warrior.action();
//被击中,进入死亡状态
_beShotDead(warrior);
warrior.action();
}
运行结果

示例分析
我们来看看这段程序的问题。
- 实现类Warrior用了大量的switch...case判断,增加了大量代码,可读性和可维护性差。
- 扩展性差。
如果要增加1个状态,则beNormal、beInvincible、bePower、dead方法中都要增加判断条件,这不符合开闭原则。
使用状态模式
类图

代码
State
var State = YYC.AClass({
Public: {
setContext: function (context) {
this.P_context = context;
}
},
Protected: {
P_context: null
},
Abstract: {
beNormal: function () {
},
beInvincible: function () {
},
bePower: function () {
},
dead: function () {
}
}
});
NormalState
var NormalState = YYC.Class(State, {
Public: {
//*在具体状态类中进行触发状态的事件判断
beNormal: function () {
if (this.P_context.warrior.timeOver) {
//本状态逻辑
console.log("恢复正常");
}
},
beInvincible: function () {
if (this.P_context.warrior.getInvincible) {
//过度到无敌状态的逻辑
this.P_context.setState(Context.InvincibleState);
this.P_context.beInvincible();
}
},
bePower: function () {
if (this.P_context.warrior.getPower) {
//过度到火力增强状态的逻辑
this.P_context.setState(Context.PowerState);
this.P_context.bePower();
}
},
dead: function () {
if (this.P_context.warrior.beShotDead) {
//过度到死亡状态的逻辑
this.P_context.setState(Context.DeadState);
this.P_context.dead();
}
}
}
});
InvincibleState
var InvincibleState = YYC.Class(State, {
Public: {
beNormal: function () {
if (this.P_context.warrior.timeOver) {
this.P_context.setState(Context.NormalState);
this.P_context.beNormal();
}
},
beInvincible: function () {
if (this.P_context.warrior.getInvincible) {
console.log("无敌");
}
},
bePower: function () {
if (this.P_context.warrior.getPower) {
this.P_context.setState(Context.PowerState);
this.P_context.bePower();
}
},
dead: function () {
//都无敌了当然不会死亡
}
}
});
PowerState
var PowerState = YYC.Class(State, {
Public: {
beNormal: function () {
if (this.P_context.warrior.timeOver) {
this.P_context.setState(Context.NormalState);
this.P_context.beNormal();
}
},
beInvincible: function () {
if (this.P_context.warrior.getInvincible) {
this.P_context.setState(Context.InvincibleState);
this.P_context.beInvincible();
}
},
bePower: function () {
if (this.P_context.warrior.getPower) {
console.log("火力增强");
}
},
dead: function () {
if (this.P_context.warrior.beShotDead) {
this.P_context.setState(Context.DeadState);
this.P_context.dead();
}
}
}
});
DeadState
var DeadState = YYC.Class(State, {
Public: {
beNormal: function () {
//不能起死回生
},
beInvincible: function () {
//挂都挂了,还怎么无敌
},
bePower: function () {
//挂都挂了,还怎么火力增强
},
dead: function () {
if (this.P_context.warrior.beShotDead) {
console.log("死亡");
}
}
}
});
Context
var Context = YYC.Class({
Init: function (warrior) {
this.warrior = warrior;
},
Private: {
_state: null
},
Public: {
warrior: null,
setState: function (state) {
this._state = state;
//把当前的上下文通知到当前状态类对象中
this._state.setContext(this);
},
beNormal: function () {
this._state.beNormal();
},
beInvincible: function () {
this._state.beInvincible();
},
bePower: function () {
this._state.bePower();
},
dead: function () {
this._state.dead();
}
},
Static: {
NormalState: new NormalState(),
InvincibleState: new InvincibleState(),
PowerState: new PowerState(),
DeadState: new DeadState()
}
});
Warrior
var Warrior = YYC.Class({
Init: function () {
this._context = new Context(this);
//设置初始状态
this._context.setState(Context.NormalState);
},
Private: {
_context: null
},
Public: {
//*事件标志
getInvincible: false,
getPower: false,
timeOver: false,
beShotDead: false,
action: function () {
this._context.beNormal();
this._context.beInvincible();
this._context.bePower();
this._context.dead();
}
}
});
场景类Client
function _resetFlag(warrior) {
warrior.getInvincible = false;
warrior.getPower = false;
warrior.imeOver = false;
warrior.beShotDead = false;
}
function _getInvincible(warrior) {
_resetFlag(warrior);
warrior.getInvincible = true;
}
function _getPower(warrior) {
_resetFlag(warrior);
warrior.getPower = true;
}
function _beShotDead(warrior) {
_resetFlag(warrior);
warrior.beShotDead = true;
}
function main() {
var warrior = new Warrior();
//获得无敌道具,进入无敌状态
_getInvincible(warrior);
warrior.action();
//获得火力增强道具,进入火力增强状态
_getPower(warrior);
warrior.action();
//被击中,进入死亡状态
_beShotDead(warrior);
warrior.action();
}
运行结果

示例分析
将触发状态的事件判断移到Warrior类中
目前是在具体状态类中进行触发状态的事件判断,这样造成了重复判断。可以将判断提出来,放到Warrior类的action中进行判断:
Warrior
action: function () {
if (this.timeOver) {
this._context.beNormal();
}
else if (this.getInvincible) {
this._context.beInvincible();
}
else if (this.getPower) {
this._context.bePower();
}
else if (this.beShotDead) {
this._context.dead();
}
}
NormalState(其它三个具体状态类做相同的重构)
var NormalState = YYC.Class(State, {
Public: {
beNormal: function () {
//本状态逻辑
console.log("恢复正常");
},
beInvincible: function () {
//过度到无敌状态的逻辑
this.P_context.setState(Context.InvincibleState);
this.P_context.beInvincible();
},
bePower: function () {
//过度到火力增强状态的逻辑
this.P_context.setState(Context.PowerState);
this.P_context.bePower();
},
dead: function () {
//过度到死亡状态的逻辑
this.P_context.setState(Context.DeadState);
this.P_context.dead();
}
}
});
局限性
如果不同状态转换为同一状态的触发事件不同,那么就不能把触发状态的事件移到Warrior类中,而需要在具体状态类中判断。
例如,现在从NORMAL状态转换到POWER状态的触发事件为“获得火力增强道具”,而从INVINCIBLE状态转换到POWER状态的触发事件为“持续时间结束且获得火力增强道具”:

那么就不能在Warrior中进行统一的事件判断了,而应该在具体状态类NormalState、InvincibleState类中判断:
NormalState
bePower: function () {
if (this.P_context.warrior.getPower) {
this.P_context.setState(Context.PowerState);
this.P_context.bePower();
}
},
InvincibleState
bePower: function () {
if (this.P_context.warrior.timeOver && this.P_context.warrior.getPower) {
this.P_context.setState(Context.PowerState);
this.P_context.bePower();
}
},
结论
不同状态转换为同一状态的触发事件相同,则可以将触发状态的事件判断放到调用Context的类中;否则将触发状态的事件判断放到具体状态类中。
参考资料
《设计模式之禅》
Javascript设计模式之我见:状态模式的更多相关文章
- [转] JavaScript设计模式之发布-订阅模式(观察者模式)-Part1
<JavaScript设计模式与开发实践>读书笔记. 发布-订阅模式又叫观察者模式,它定义了对象之间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖它的对象都将得到通知. 例如 ...
- javascript设计模式--策略模式
javascript策略模式总结 1.什么是策略模式? 策略模式的定义是:定义一系列的算法,把他们独立封装起来,并且可以相互替换. 例如我们需要写一段代码来计算员工的奖金.当绩效为a时,奖金为工资的5 ...
- JavaScript 设计模式: 发布者-订阅者模式
JavaScript 设计模式: 发布者-订阅者模式 发布者-订阅者模式 https://github.com/Kelichao/javascript.basics/issues/22 https:/ ...
- Javascript设计模式之我见:迭代器模式
大家好!本文介绍迭代器模式及其在Javascript中的应用. 模式介绍 定义 提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示. 类图及说明 Iterator抽象迭代器 抽象迭代器负 ...
- JavaScript设计模式-单例模式、模块模式(转载 学习中。。。。)
(转载地址:http://technicolor.iteye.com/blog/1409656) 之前在<JavaScript小特性-面向对象>里面介绍过JavaScript面向对象的特性 ...
- Javascript设计模式之我见:观察者模式
大家好!本文介绍观察者模式及其在Javascript中的应用. 模式介绍 定义 定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新. 类图及说明 S ...
- 【读书笔记】读《JavaScript设计模式》之代理模式
一.定义 代理是一个对象,它可以用来控制对另一个对象的访问.它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象.另外那个对象通常称为本体.代理可以代替其实体被实例化,并使其可被远程访 ...
- 【读书笔记】读《JavaScript设计模式》之门面模式
一.前言 门面模式,也称Facade(外观)模式.核心的两点作用—— 1> 简化类的接口(让接口变得更加容易理解.容易应用.更加符合对应业务),来掩盖一个非常不同或者复杂的实现 2> 消除 ...
- 【读书笔记】读《JavaScript设计模式》之工厂模式
一个类或对象中往往会包含别的对象.在创建这种成员对象时,你可能习惯于使用常规方式,也即用new关键字和类构造函数.问题在于这回导致相关的两个类之间产生依赖性. 工厂模式用于消除这两个类之间的依赖性,它 ...
随机推荐
- 给你的Mr.Right画张择偶地图像
爱一个人就算做不到爱他的全部,至少也应该尊重他的真实,而不是苛求他变成你想要的样子. 娶妻当娶郭芙蓉,经典语录.我是郭芙蓉,我不会武功,我来自江湖,我与众不同.再苦再累,就当自己是二百五,再难再险,就 ...
- XMLHttpRequest的跨域请求
缘起 由于浏览器的同源策略,非同源不可请求. 但是,在实践当中,经常会出现需要跨域请求资源的情况,比较典型的例如某个子域名向负责进行用户验证的子域名请求用户信息等应用. 以前要实现跨域访问,可以通过J ...
- 学习调用WCF服务的各种方法
1.开发工具调用WCF 这中方法很方便也很简单,很多工作VS就帮我们完成了.相信大家也不会对这种方法陌生.这里简单提一下.打开VS,在项目中添加服务引用: 在config中自动声明了有关服务的节点信息 ...
- C#程序员开发WinForm必须知道的 Window 消息大全(转)
消息,就是指Windows发出的一个通知,告诉应用程序某个事情发生了.例如,单击鼠标.改变窗口尺寸.按下键盘上的一个键都会使Windows发送一个消息给应用程序. 消息本身是作为一个记录传递给应用程 ...
- Swift学习笔记--变量与常量
1.Swift是一门强类型语言,不能为变量赋予其自身数据类型之外的值: 2.声明变量使用var关键字,声明常量使用let关键字: 3.声明变量或常量时没有对其指定类型且赋予了初值,则编译器会自动推断常 ...
- 17110 Divisible(basic)
17110 Divisible 时间限制:1000MS 内存限制:65535K 题型: 编程题 语言: 无限制 Description Given n + m integers, I1,I2,. ...
- 关于HADOOP HA 中DFSZKFC的理解
[转自uc技术博客:http://tech.uc.cn/?p=252] FC是要和NN一一对应的,两个NN就要部署两个FC.它负责监控NN的状态,并及时的把状态信息写入ZK.它通过一个独立线程周期性的 ...
- Start cluster zookeeper in shell script
cat start-zookeeper.sh #!bin/sh for node in namenode01 datanode01 datanode02 do echo "s ...
- Oracle错误日志:ORA-00257
今天下午刚启动程序,就报以下错误: Caused by: java.sql.SQLException: ORA-00257: 归档程序错误.在释放之前仅限于内部连接. 经查,是oracle数据库日志满 ...
- Hadoop 2.0中单点故障解决方案总结
Hadoop 1.0内核主要由两个分支组成:MapReduce和HDFS,众所周知,这两个系统的设计缺陷是单点故障,即MR的JobTracker和HDFS的NameNode两个核心服务均存在单点问题, ...