深入理解JavaScript系列(43):设计模式之状态模式详解


Posted in Javascript onMarch 04, 2015

介绍

状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。

正文

举个例子,就比如我们平时在下载东西,通常就会有好几个状态,比如准备状态(ReadyState)、下载状态(DownloadingState)、暂停状态(DownloadPausedState)、下载完毕状态(DownloadedState)、失败状态(DownloadFailedState),也就是说在每个状态都只可以做当前状态才可以做的事情,而不能做其它状态能做的事儿。

由于State模式描述了下载(Download)如何在每一种状态下表现出不同的行为。这一模式的关键思想就是引入了一个叫做State的抽象类(或JS里的函数)来表示下载状态,State函数(作为原型)为每个状态的子类(继承函数)声明了一些公共接口。其每个继承函数实现与特定状态相关的行为,比如DownloadingState和DownloadedState分别实现了正在下载和下载完毕的行为。这些行为可以通过Download来来维护。

让我们来实现一把,首先定义作为其他基础函数的原型的State函数:

var State = function () {
};
State.prototype.download = function () {

    throw new Error("该方法必须被重载!");

};
State.prototype.pause = function () {

    throw new Error("该方法必须被重载!");

};
State.prototype.fail = function () {

    throw new Error("该方法必须被重载!");

};
State.prototype.finish = function () {

    throw new Error("该方法必须被重载!");

};

我们为State的原型定义了4个方法接口,分别对应着下载(download)、暂停(pause)、失败(fail)、结束(finish)以便子函数可以重写。

在编写子函数之前,我们先来编写一个ReadyState函数,以便可以将状态传递给第一个download状态:

var ReadyState = function (oDownload) {

    State.apply(this);

    this.oDownload = oDownload;

};
ReadyState.prototype = new State();
ReadyState.prototype.download = function () {

    this.oDownload.setState(this.oDownload.getDownloadingState());

    // Ready以后,可以开始下载,所以设置了Download函数里的状态获取方法

 console.log("Start Download!");

};
ReadyState.prototype.pause = function () {

    throw new Error("还没开始下载,不能暂停!");

};
ReadyState.prototype.fail = function () {

    throw new Error("文件还没开始下载,怎么能说失败呢!");

};
ReadyState.prototype.finish = function () {

    throw new Error("文件还没开始下载,当然也不能结束了!");

};

该函数接收了一个Download维护函数的实例作为参数,Download函数用于控制状态的改变和获取(类似于中央控制器,让外部调用),ReadyState重写了原型的download方法,以便开始进行下载。我们继续来看Download函数的主要功能:

var Download = function () {

    this.oState = new ReadyState(this);

};
Download.prototype.setState = function (oState) {

    this.oState = oState;

};
// 对外暴露的四个公共方法,以便外部调用
Download.prototype.download = function () {

    this.oState.download();

};
Download.prototype.pause = function () {

    this.oState.pause();

};
Download.prototype.fail = function () {

    this.oState.fail();

};
Download.prototype.finish = function () {

    this.oState.finish();

};
//获取各种状态,传入当前this对象

Download.prototype.getReadyState = function () {

    return new ReadyState(this);

};
Download.prototype.getDownloadingState = function () {

    return new DownloadingState(this);

};
Download.prototype.getDownloadPausedState = function () {

    return new DownloadPausedState(this);

};
Download.prototype.getDownloadedState = function () {

    return new DownloadedState(this);

};
Download.prototype.getDownloadedFailedState = function () {

    return new DownloadFailedState(this);

};

Download函数的原型提供了8个方法,4个是对用于下载状态的操作行为,另外4个是用于获取当前四个不同的状态,这4个方法都接收this作为参数,也就是将Download实例自身作为一个参数传递给处理该请求的状态对象(ReadyState 以及后面要实现的继承函数),这使得状态对象比必要的时候可以访问oDownlaod。

接下来,继续定义4个相关状态的函数:

var DownloadingState = function (oDownload) {

    State.apply(this);

    this.oDownload = oDownload;

};
DownloadingState.prototype = new State();
DownloadingState.prototype.download = function () {

    throw new Error("文件已经正在下载中了!");

};
DownloadingState.prototype.pause = function () { this.oDownload.setState(this.oDownload.getDownloadPausedState());

    console.log("暂停下载!");

};
DownloadingState.prototype.fail = function () { this.oDownload.setState(this.oDownload.getDownloadedFailedState());

    console.log("下载失败!");

};
DownloadingState.prototype.finish = function () {

    this.oDownload.setState(this.oDownload.getDownloadedState());

    console.log("下载完毕!");

};

DownloadingState的主要注意事项就是已经正在下载的文件,不能再次开始下载了,其它的状态都可以连续进行。

var DownloadPausedState = function (oDownload) {

    State.apply(this);

    this.oDownload = oDownload;

};
DownloadPausedState.prototype = new State();
DownloadPausedState.prototype.download = function () {

    this.oDownload.setState(this.oDownload.getDownloadingState());

    console.log("继续下载!");

};
DownloadPausedState.prototype.pause = function () {

    throw new Error("已经暂停了,咋还要暂停呢!");

};
DownloadPausedState.prototype.fail = function () { this.oDownload.setState(this.oDownload.getDownloadedFailedState());

    console.log("下载失败!");

};
DownloadPausedState.prototype.finish = function () {

    this.oDownload.setState(this.oDownload.getDownloadedState());

    console.log("下载完毕!");

};

DownloadPausedState函数里要注意的是,已经暂停的下载,不能再次暂停。
var DownloadedState = function (oDownload) {

    State.apply(this);

    this.oDownload = oDownload;

};
DownloadedState.prototype = new State();
DownloadedState.prototype.download = function () {

    this.oDownload.setState(this.oDownload.getDownloadingState());

    console.log("重新下载!");

};
DownloadedState.prototype.pause = function () {

    throw new Error("对下载完了,还暂停啥?");

};
DownloadedState.prototype.fail = function () {

    throw new Error("都下载成功了,咋会失败呢?");

};
DownloadedState.prototype.finish = function () {

    throw new Error("下载成功了,不能再为成功了吧!");

};

DownloadedState函数,同理成功下载以后,不能再设置finish了,只能设置重新下载状态。

var DownloadFailedState = function (oDownload) {

    State.apply(this);

    this.oDownload = oDownload;

};
DownloadFailedState.prototype = new State();
DownloadFailedState.prototype.download = function () {

    this.oDownload.setState(this.oDownload.getDownloadingState());

    console.log("尝试重新下载!");

};
DownloadFailedState.prototype.pause = function () {

    throw new Error("失败的下载,也不能暂停!");

};
DownloadFailedState.prototype.fail = function () {

    throw new Error("都失败了,咋还失败呢!");

};
DownloadFailedState.prototype.finish = function () {

    throw new Error("失败的下载,肯定也不会成功!");

};

同理,DownloadFailedState函数的失败状态,也不能再次失败,但可以和finished以后再次尝试重新下载。

调用测试代码,就非常简单了,我们在HTML里演示吧,首先是要了jquery,然后有3个按钮分别代表:开始下载、暂停、重新下载。(注意在Firefox里用firebug查看结果,因为用了 console.log方法)。

<html>

<head>

    <link type="text/css" rel="stylesheet" href="http://www.cnblogs.com/css/style.css" />

    <title>State Pattern</title>

    <script type="text/javascript" src="/jquery.js"></script>

    <script type="text/javascript" src="Download.js"></script>

    <script type="text/javascript" src="states/State.js"></script>

    <script type="text/javascript" src="states/DownloadFailedState.js"></script>

    <script type="text/javascript" src="states/DownloadPausedState.js"></script>

    <script type="text/javascript" src="states/DownloadedState.js"></script>

    <script type="text/javascript" src="states/DownloadingState.js"></script>

    <script type="text/javascript" src="states/ReadyState.js"></script>

</head>

<body>

    <input type="button" value="开始下载" id="download_button" />

    <input type="button" value="暂停" id="pause_button" />

    <input type="button" value="重新下载" id="resume_button" />

    <script type="text/javascript">

        var oDownload = new Download();

        $("#download_button").click(function () {

            oDownload.download();

        });
        $("#pause_button").click(function () {

            oDownload.pause();

        });
        $("#resume_button").click(function () {

            oDownload.download();

        });

    </script>

</body>

</html>

总结

状态模式的使用场景也特别明确,有如下两点:

1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
2.一个操作中含有大量的分支语句,而且这些分支语句依赖于该对象的状态。状态通常为一个或多个枚举常量的表示。

Javascript 相关文章推荐
flash javascript之间的通讯方法小结
Dec 20 Javascript
jquery attr方法获取input的checked属性问题
May 26 Javascript
js实现网页右上角滑出会自动消失大幅广告的方法
Feb 27 Javascript
jquery插件orbit.js实现图片折叠轮换特效
Apr 14 Javascript
实例讲解javascript注册事件处理函数
Jan 09 Javascript
jQuery实现删除li节点的方法
Dec 06 Javascript
ES6新特性之变量和字符串用法示例
Apr 01 Javascript
详解webpack+es6+angular1.x项目构建
May 02 Javascript
怎样使你的 JavaScript 代码简单易读(推荐)
Apr 16 Javascript
Vue动态面包屑功能的实现方法
Jul 01 Javascript
selenium+java中用js来完成日期的修改
Oct 31 Javascript
vue-cli3项目配置eslint代码规范的完整步骤
Sep 10 Javascript
深入理解JavaScript系列(42):设计模式之原型模式详解
Mar 04 #Javascript
javascript 动态创建表格的2种方法总结
Mar 04 #Javascript
深入理解JavaScript系列(41):设计模式之模板方法详解
Mar 04 #Javascript
深入理解JavaScript系列(40):设计模式之组合模式详解
Mar 04 #Javascript
百度地图自定义控件分享
Mar 04 #Javascript
jQuery实现仿淘宝带有指示条的图片转动切换效果完整实例
Mar 04 #Javascript
深入理解JavaScript系列(39):设计模式之适配器模式详解
Mar 04 #Javascript
You might like
session在php5.3中的变化 session_is_registered() is deprecated in
2013/11/12 PHP
php实现网站顶踩功能的完整前端代码
2015/07/19 PHP
php mysql操作mysql_connect连接数据库实例详解
2016/12/26 PHP
php图形jpgraph操作实例分析
2017/02/22 PHP
Eclipse PHPEclipse 配置的具体步骤
2017/08/08 PHP
游戏人文件夹程序 ver 4.03
2006/07/14 Javascript
javascript动态改变img的src属性图片不显示的解决方法
2010/10/20 Javascript
jquery 操作日期、星期、元素的追加的实现代码
2012/02/07 Javascript
jquery阻止冒泡事件使用模拟事件
2013/09/06 Javascript
node.js下when.js 的异步编程实践
2014/12/03 Javascript
javascript阻止事件冒泡和浏览器的默认行为
2017/01/21 Javascript
js调用刷新界面的几种方式
2017/05/03 Javascript
angular4强制刷新视图的方法
2018/10/09 Javascript
Vue路由模块化配置的完整步骤
2019/08/14 Javascript
layui radio单选限制下一个radio单选的实例
2019/09/03 Javascript
记录一次websocket封装的过程
2020/11/23 Javascript
Python实现子类调用父类的方法
2014/11/10 Python
pycharm安装和首次使用教程
2018/08/27 Python
python利用百度AI实现文字识别功能
2018/11/27 Python
Python中的Socket 与 ScoketServer 通信及遇到问题解决方法
2019/04/01 Python
Cython编译python为so 代码加密示例
2019/12/23 Python
5款实用的python 工具推荐
2020/10/13 Python
全球速卖通:AliExpress(国际版淘宝)
2017/09/20 全球购物
印度尼西亚最好的小工具在线商店:Erafone.com
2019/03/26 全球购物
先进集体获奖感言
2014/02/13 职场文书
初一新生军训方案
2014/05/22 职场文书
简易离婚协议书范本2014
2014/10/15 职场文书
群众路线批评与自我批评发言稿
2014/10/16 职场文书
构建和谐校园倡议书
2015/01/19 职场文书
网络妈妈观后感
2015/06/08 职场文书
汉语拼音教学反思
2016/02/22 职场文书
2016年大学生社区服务活动总结
2016/04/06 职场文书
八年级作文之友谊
2019/12/02 职场文书
用Python实现Newton插值法
2021/04/17 Python
使用Python的开发框架Brownie部署以太坊智能合约
2021/05/28 Python
OpenCV-Python实现人脸磨皮算法
2021/06/07 Python