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关键字和类构造函数.问题在于这回导致相关的两个类之间产生依赖性. 工厂模式用于消除这两个类之间的依赖性,它 ...
随机推荐
- 基于Ubuntu虚拟机安装edx-platform
基于Ubuntu虚拟机安装edx-platform 一. 前提准备 1. 虚拟机中安装Ubuntu12.04,然后再使用Vagrant方式搭建开发环境,请确保这个虚拟机可以使用2GB的内存,否则容 ...
- 长文件名导致的0x80070057
今天遇到件怪事. 把一个视频集(86G)从电脑硬盘转移动硬盘的时候里面时报里面的两个文件夹里的视频和字幕不能复制 错误代码0x80070057 这个视频集是从校内PT是下下来的,电脑是联想Y560-w ...
- Nodejs断言测试
var assert = require('assert');/*node中,我们可以使用assert模块来测试代码.equal()和notEqual()分别作相等性和不等性的判断,第一个参数是期望值 ...
- 线程本地存储TLS(Thread Local Storage)的原理和实现——分类和原理
原文链接地址:http://www.cppblog.com/Tim/archive/2012/07/04/181018.html 本文为线程本地存储TLS系列之分类和原理. 一.TLS简述和分类 我们 ...
- Gradle系列教程之依赖管理(转)
转自Lippi-浮生志 :http://ezlippi.com/blog/2015/05/gradle-dependency-management.html 这一章我将介绍Gradle对依赖管理的强大 ...
- subline 快捷键
subline 快捷键 安装 pretty css html 后1,CTRl+ shift +H 格式化代码
- 基于XML配置的Spring MVC 简单的HelloWorld实例应用
1.1 问题 使用Spring Web MVC构建helloworld Web应用案例. 1.2 方案 解决本案例的方案如下: 1. 创建Web工程,导入Spring Web MVC相关开发包. Sp ...
- redis master配置了密码进行主从同步
1.如果master不设置密码,那么直接在slave服务器配置slaveof即可 配置如下 #slaveof ip 端口 slaveof 配置好我们看下redis的日志 看是否同步成功 :S Jan ...
- linux系统下设置oracle开机自动启动
在Linux系统中,安装好oracle数据库服务后,并不像在Windows系统下一样,oracle服务在默认情况下会随时系统的启动自动启动.Linux系统中,是需要用户去手动进行设置,才能实现orac ...
- dipole antenna simulation by FEKO
新建变量 建立模型 设置频率 馈电设置为wire port ,Edge 选中振子,从中心馈电. 设置输入信号 Mesh. run solver.在post feko中查看相关结果