大家好!本文介绍状态模式及其在Javascript中的应用。

模式介绍

定义

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

状态模式主要解决的是控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

类图及说明

State:抽象状态

接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换

ConcreState:具体状态

每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理。通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态。

Context:环境 

定义客户端需要的接口,并且负责具体状态的切换。

应用场景

  • 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
  • 代码中包含大量与对象状态有关的条件语句:一个操作中含有庞大的多分支的条件(if else(或switch case)语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。 State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

优点

  1. 避免了过多的 swith…case 或者 if..else 语句的使用,避免了程序的复杂性
  2. 很好的使用体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就增加子类,你要修改状态,你只修改一个子类就可以了
  3. 封装性非常好,这也是状态模式的基本要求。状态变换放置到了类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。

缺点

  1. 具体状态类可能会有很多个,不好管理。

状态模式在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的类中;否则将触发状态的事件判断放到具体状态类中。

参考资料

《设计模式之禅》

<<Head First设计模式>>之状态模式学习篇

Javascript设计模式之我见:状态模式的更多相关文章

  1. [转] JavaScript设计模式之发布-订阅模式(观察者模式)-Part1

    <JavaScript设计模式与开发实践>读书笔记. 发布-订阅模式又叫观察者模式,它定义了对象之间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖它的对象都将得到通知. 例如 ...

  2. javascript设计模式--策略模式

    javascript策略模式总结 1.什么是策略模式? 策略模式的定义是:定义一系列的算法,把他们独立封装起来,并且可以相互替换. 例如我们需要写一段代码来计算员工的奖金.当绩效为a时,奖金为工资的5 ...

  3. JavaScript 设计模式: 发布者-订阅者模式

    JavaScript 设计模式: 发布者-订阅者模式 发布者-订阅者模式 https://github.com/Kelichao/javascript.basics/issues/22 https:/ ...

  4. Javascript设计模式之我见:迭代器模式

    大家好!本文介绍迭代器模式及其在Javascript中的应用. 模式介绍 定义 提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象内部表示. 类图及说明 Iterator抽象迭代器 抽象迭代器负 ...

  5. JavaScript设计模式-单例模式、模块模式(转载 学习中。。。。)

    (转载地址:http://technicolor.iteye.com/blog/1409656) 之前在<JavaScript小特性-面向对象>里面介绍过JavaScript面向对象的特性 ...

  6. Javascript设计模式之我见:观察者模式

    大家好!本文介绍观察者模式及其在Javascript中的应用. 模式介绍 定义 定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新. 类图及说明 S ...

  7. 【读书笔记】读《JavaScript设计模式》之代理模式

    一.定义 代理是一个对象,它可以用来控制对另一个对象的访问.它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象.另外那个对象通常称为本体.代理可以代替其实体被实例化,并使其可被远程访 ...

  8. 【读书笔记】读《JavaScript设计模式》之门面模式

    一.前言 门面模式,也称Facade(外观)模式.核心的两点作用—— 1> 简化类的接口(让接口变得更加容易理解.容易应用.更加符合对应业务),来掩盖一个非常不同或者复杂的实现 2> 消除 ...

  9. 【读书笔记】读《JavaScript设计模式》之工厂模式

    一个类或对象中往往会包含别的对象.在创建这种成员对象时,你可能习惯于使用常规方式,也即用new关键字和类构造函数.问题在于这回导致相关的两个类之间产生依赖性. 工厂模式用于消除这两个类之间的依赖性,它 ...

随机推荐

  1. 网站的SEO

    提高网站SEO排名的策略除了要有高质量的内容,还有几种方案可以使用 1.关键词的设定 合适的关键词可以提升搜索引擎中的排名 ①最重要的是html中的title标签,这也是一个页面的最重要的概括,所以尽 ...

  2. org.dom4j.documentexception异常

    org.dom4j.documentexception 解决: 设置xml文件编码格式:<?xml version="1.0" encoding="UTF-8&qu ...

  3. Microsoft Community

    一.简介 Microsoft Community 是一个免费社区和讨论论坛,项目开发遇到的问题可以在这里进行提出和解答. 二.地址 http://answers.microsoft.com/zh-ha ...

  4. SCAU巡逻的士兵

    1142 巡逻的士兵 Description 有N个士兵站成一队列, 现在需要选择几个士兵派去侦察. 为了选择合适的士兵, 多次进行如下操作: 如果队列超过三个士兵, 那么去除掉所有站立位置为奇数的士 ...

  5. js 操作select和option

    js 操作select和option 1.动态创建select function createSelect(){ var mySelect = document.createElement_x(&qu ...

  6. Ubuntu 安装JDK并配置成为默认的JDK

    Ubuntu安装JDK 系统版本:Ubuntu 15.04 x64 JDK版本:jdk-8u60-linux-x64 1.查看系统位数,输入以下命令即可 getconf LONG_BIT 2.下载对应 ...

  7. Maximum Subsequence Sum(接上篇)

    Given a sequence of K integers { N1, N2, ..., NK }. A continuous subsequence is defined to be { Ni, ...

  8. redis unwatch discard

    UNWATCH UNWATCH 取消 WATCH 命令对所有 key 的监视. 如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行UNWATC ...

  9. 如何做好presentation

    1.全心投入 要么不做,要做就做好 承诺自己会花时间好好准备自己的演讲,投入专注的精力. 人们可以通过练习使自己成为很好的演讲者. 2分析你的观众 他们想听什么? 3.组织你的想法 让语言简单 让观众 ...

  10. 动手学习TCP: 环境搭建

    前一段时间通过Wireshark抓包,定位了一个客户端和服务器之间数据传输的问题.最近就抽空看了看<TCP/IP详解 卷1>中关于TCP的部分,书中用了很多例子展示了TCP/IP协议中的一 ...