大家好!本文介绍状态模式及其在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. JavaScript Patterns 2.9 Coding Conventions

    It’s important to establish and follow coding conventions—they make your code consistent, predictabl ...

  2. 【mysql】索引的优化

    写在前面的话 查询容易,优化不易,且写且珍惜 mysql结构 从MySQL逻辑架构来看,MySQL有三层架构,第一层连接,第二层查询解析.分析.优化.视图.缓存,第三层,存储引擎 MySQL有哪些索引 ...

  3. C# Socket和TCP连接的区别

    网络通信七层参考模型介绍: 物理层: HUB,网线 链路层: MAC,ARP,交换机 网络层:IP,ICMP,IGMP,路由器 传输层: TCP,UDP 会话层: HTTP,SMTP,FTP,POP3 ...

  4. Hibernate案例-------基于xml配置,使用Hibernate实现对员工表的增、删、改、查功能

    1.1 问题 使用Hibernate实现对员工表的增.删.改.查. 1.2 方案 Hibernate使用步骤: 导入Hibernate包,以及数据库驱动包. 引入Hibernate主配置文件hiber ...

  5. 图片延迟加载(lazyload)的实现原理

    此前在浏览一些网站的时候,发现他们网站的图片都是你“鼠标”滚到哪,图片才会加载显示.当时觉得好神奇,怎么会这么“跟手”呢. 核心原理是: 1 设置一个定时器,计算每张图片是否会随着滚动条的滚动,而出现 ...

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

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

  7. Linux命令的类型

    1.内建命令: 由shell程序自带的命令,最常见的有cd.pwd等. 使用type命令即可查看命令属于哪种,比如: #type cd cd is a shell builtin ————>看到 ...

  8. noip2014提高组day2二题题解-rLq

    (又是昨天的作业……本题写于昨天) (这破题都做这么久,我是不是吃枣药丸……) (好吧这是一道图论题呢) 本题地址:http://www.luogu.org/problem/show?pid=2296 ...

  9. [转]微服务(Microservice)那点事

    WHAT – 什么是微服务 微服务简介 这次参加JavaOne2015最大的困难就是听Microservice相关的session,无论内容多么水,只要题目带microservice,必定报不上名,可 ...

  10. IO流的练习 —— 创建用户注册、登陆案例

    把上次用集合做的时候的分析拿出来 需求: 用户登录注册案例 按照如下操作,可以让我们更符合面向对象思想: A:这个案例有哪些类 B:每个类中又有哪些东西 C:类与类之间的关系 分析: A:这个案例有哪 ...