大家好!本文介绍状态模式及其在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. android中实现view可以滑动的六种方法

    在android开发中,经常会遇到一个view需要它能够支持滑动的需求.今天就来总结实现其滑动的六种方法.其实每一种方法的 思路都是一样的,即:监听手势触摸的坐标来实现view坐标的变化,从而实现vi ...

  2. 手动将自定制的WebPart部署到 SharePoint 2010 中

    1.搭建好开发环境,建立webpart工程,写代码. 2.修改assembly.cs文件   在部署前,需要修改assembly文件,增加以下两句: using System.Security; [a ...

  3. Effective Java 02 Consider a builder when faced with many constructor parameters

    Advantage It simulates named optional parameters which is easily used to client API. Detect the inva ...

  4. 系统调用wait、waitpid和exec函数

    本文介绍了Linux下的进程的一些概念,并着重讲解了与Linux进程管理相关的重要系统调用wait,waitpid和exec函数族,辅助一些例程说明了它们的特点和使用方法. 1.7 背景 在前面的文章 ...

  5. Swift学习笔记--变量与常量

    1.Swift是一门强类型语言,不能为变量赋予其自身数据类型之外的值: 2.声明变量使用var关键字,声明常量使用let关键字: 3.声明变量或常量时没有对其指定类型且赋予了初值,则编译器会自动推断常 ...

  6. Windows Live Writer离线编写博客

    WLW最新版本为2012,官网下载 Windows Live Writer配置步骤 使用Windows Live Writer 2012和Office Word 2013 发布文章到博客园全面总结 L ...

  7. dba诊断之lock

    --产生锁的详细信息 select a.session_id, c.SERIAL#,d.spid, os_user_name, b.object_name,locked_mode,    c.sql_ ...

  8. JavaScript的函数重载

    java语言中函数的重载和重写可谓是很重要的概念,所以在写js的时候时不时的会想到这种用法,重写先不说,这里只说重载.. <script language="JavaScript&qu ...

  9. C语言使用宏实现2个变量的交换

    记录哪个方法更普适,更高效,这些方法不包括使用函数的方法,如果使用函数的话,使用指针的方法更合适. 使用中间变量 形如 int tmp, tmp = a; a=b; b = tmp; #define ...

  10. cnblogs美化及插件

    1.vp计数 http://www.amazingcounters.com 2.来源地图 http://clustrmaps.com 2.1来源地图 http://www.flagcounter.co ...