JavaScript状态模式

JavaScript状态模式

  • 1 什么是状态模式
  • 2 使用状态模式改造电灯程序
  • 3 缺少抽象类的变通方式
  • 4 示例:文件上传
    • 4.1 场景描述
    • 4.2 代码过程

1 什么是状态模式

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

比如说这样一个场景:有一个电灯,电灯上面只有一个开关。当电灯开着的时候,此时按下开关,电灯会切换到关闭状态;再按一次开关,电灯又将被打开。同一个开关按钮,在不同的状态下,表现出来的行为是不一样的。

我们用代码来描述上面的场景:

// 定义一个Light类
var Light = function () {this.state = "off"; // 给电灯设置初始状态 offthis.button = null; // 电灯开关按钮
};// 在页面中创建一个真实的button节点
Light.prototype.init = function () {var button = document.createElement("button"), // 创建一个开关按钮self = this;button.innerHTML = "开关";this.button = document.body.appendChild(button);// 开关被按下的事件this.button.onclick = function () {self.buttonWasPressed();};
};// 开关被按下的行为
Light.prototype.buttonWasPressed = function () {// 如果当前是关灯状态,按下开关表示开灯if (this.state === "off") {console.log("开灯");this.state = "on";} else if (this.state === "on") {// 如果当前是开灯状态,按下开关表示关灯console.log("关灯");this.state = "off";}
};var light = new Light();
light.init();

但是灯的种类是多种多样的,另外一种电灯,这种电灯也只有一个开关,但它的表现是:第一次按下打开弱光,第二次按下打开强光,第三次才是关闭电灯,现在我们改造上面的代码来完成这种新型电灯的制造:

Light.prototype.buttonWasPressed = function () {if (this.state === "off") {console.log("弱光");this.state = "weakLight";} else if (this.state === "weakLight") {console.log("强光");this.state = "strongLight";} else if (this.state === "strongLight") {console.log("关灯");this.state = "off";}
};

在上面的代码中,存在一些很明显的缺点:

  • buttonWasPressed方法违反开放—封闭原则,每次新增或者修改灯光的状态,都需要改动buttonWasPressed方法中的代码,这使其成为了一个非常不稳定的方法
  • 所有跟状态有关的行为,都被封装在buttonWasPressed方法里,如果这个电灯又增加了其他光的种类,那这个方法会越来越庞大
  • 状态的切换不明显,仅仅表现为改变state,容易漏掉某些状态
  • 状态之间的切换关系,是靠ifelse语句,增加或者修改一个状态可能需要改变若干个操作,这使代码难以阅读和维护

2 使用状态模式改造电灯程序

状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,所以button被按下的的时候,只需要在上下文中,把这个请求委托给当前的状态对象即可,该状态对象会负责渲染它自身的行为。
在这里插入图片描述
同时我们还可以把状态的切换规则事先分布在状态类中, 这样就有效地消除了原本存在的
大量条件分支语句,代码如下:

// OffLightState:
var OffLightState = function (light) {this.light = light;
};
OffLightState.prototype.buttonWasPressed = function () {console.log("弱光"); // offLightState 对应的行为this.light.setState(this.light.weakLightState); // 切换状态到 weakLightState
};// WeakLightState:
var WeakLightState = function (light) {this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function () {console.log("强光"); // weakLightState 对应的行为this.light.setState(this.light.strongLightState); // 切换状态到 strongLightState
};// StrongLightState:
var StrongLightState = function (light) {this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function () {console.log("关灯"); // strongLightState 对应的行为this.light.setState(this.light.offLightState); // 切换状态到 offLightState
};// 改写Light类,在Light类中为每个状态类都创建一个状态对象,可以很明显的看到灯的种类
var Light = function () {this.offLightState = new OffLightState(this);this.weakLightState = new WeakLightState(this);this.strongLightState = new StrongLightState(this);this.button = null;
};// 按下按钮的事件中,将请求委托给当前持有的状态对象去执行
Light.prototype.init = function () {var button = document.createElement("button"), // 创建buttonself = this;this.button = document.body.appendChild(button);this.button.innerHTML = "开关";// 设置当前状态this.currState = this.offLightState;this.button.onclick = function () {self.currState.buttonWasPressed();};
};// 切换light对象的状态
Light.prototype.setState = function (newState) {this.currState = newState;
};var light = new Light();
light.init();

3 缺少抽象类的变通方式

在上面的代码中,在状态类中将定义一些共同的行为方法,Context最终会将请求委托给状态对象的这些方法,在这个例子里这个方法就是buttonWasPressed。无论增加了多少种状态类,它们都必须实现buttonWasPressed方法。

所以使用状态模式的时候要格外小心,如果我们编写一个状态子类时,忘记了给这个状态子类实现buttonWasPressed方法,则会在状态切换的时候抛出异常,因为Context总是把请求委托给状态对象的buttonWasPressed方法。因此我们让抽象父类的抽象方法直接抛出一个异常:

var State = function () {};
State.prototype.buttonWasPressed = function () {throw new Error("父类的 buttonWasPressed 方法必须被重写");
};
var SuperStrongLightState = function (light) {this.light = light;
};
SuperStrongLightState.prototype = new State(); // 继承抽象父类
SuperStrongLightState.prototype.buttonWasPressed = function () {// 重写 buttonWasPressed 方法console.log("关灯");this.light.setState(this.light.offLightState);
};

4 示例:文件上传

4.1 场景描述

例如,控制文件上传需要两个节点按钮,第一个用于暂停和继续上传,第二个用于删除文件

  • 当文件在扫描状态中,不能进行任何操作,既不能暂停也不能删除文件,只能等待扫描完成。扫描完成之后,根据文件的md5值判断,若确认该文件已经存在于服务器,则直接跳到上传完成状态。如果该文件的大小超过允许上传的最大值,或者该文件已经损坏,则跳往上传失败状态。剩下的情况下才进入上传中状态
  • 上传过程中可以点击暂停按钮来暂停上传,暂停后点击同一个按钮会继续上传
  • 扫描和上传过程中,点击删除按钮无效,只有在暂停、上传完成、上传失败之后,才能删除文件

假设我们使用一个插件对象帮助我们完成上传工作:

var plugin = (function () {var plugin = document.createElement("embed");plugin.style.display = "none";plugin.type = "application/txftn-webkit";plugin.sign = function () {console.log("开始文件扫描");};plugin.pause = function () {console.log("暂停文件上传");};plugin.uploading = function () {console.log("开始文件上传");};plugin.del = function () {console.log("删除文件上传");};plugin.done = function () {console.log("文件上传完成");};document.body.appendChild(plugin);return plugin;
})();

上传是一个异步的过程,所以控件会不停地调用全局函数window.external.upload,来通知目前的上传进度,控件会把当前的文件状态作为参数state塞进window.external.upload,在此例中该函数负责打印一些log

window.external.upload = function (state) {console.log(state); // 可能为 sign、uploading、done、error
};

4.2 代码过程

首先定义Upload类,在构造函数中为每种状态子类都创建一个实例对象:

var Upload = function (fileName) {this.plugin = plugin;this.fileName = fileName;this.button1 = null;this.button2 = null;this.signState = new SignState(this); // 设置初始状态为 waitingthis.uploadingState = new UploadingState(); // 上传中this.pauseState = new PauseState(this); // 暂停this.doneState = new DoneState(this); // 上传完成this.errorState = new ErrorState(this); // 上传错误this.currState = this.signState; // 设置当前状态
};

创建两个按钮,一个控制文件暂停和继续上传,一个用于删除文件:

Upload.prototype.init = function () {var that = this;this.dom = document.createElement("div");this.dom.innerHTML ="<span>文件名称:" +this.fileName +'</span><button data-action="button1">扫描中</button><button data-action="button2">删除</button>';document.body.appendChild(this.dom);this.button1 = this.dom.querySelector('[data-action="button1"]'); // 第一个按钮this.button2 = this.dom.querySelector('[data-action="button2"]'); // 第二个按钮this.bindEvent();
};

为两个按钮分别绑定点击事件,在点击了按钮之后,Context并不做任何具体的操作,而是把请求委托给当前的状态类来执行:

Upload.prototype.bindEvent = function () {var self = this;this.button1.onclick = function () {self.currState.clickHandler1();};this.button2.onclick = function () {self.currState.clickHandler2();};
};// 扫描中
Upload.prototype.sign = function () {this.plugin.sign();this.currState = this.signState;
};
// 上传中
Upload.prototype.uploading = function () {this.button1.innerHTML = "正在上传,点击暂停";this.plugin.uploading();this.currState = this.uploadingState;
};
// 暂停
Upload.prototype.pause = function () {this.button1.innerHTML = "已暂停,点击继续上传";this.plugin.pause();this.currState = this.pauseState;
};
// 上传成功
Upload.prototype.done = function () {this.button1.innerHTML = "上传完成";this.plugin.done();this.currState = this.doneState;
};
// 上传失败
Upload.prototype.error = function () {this.button1.innerHTML = "上传失败";this.currState = this.errorState;
};
// 删除
Upload.prototype.del = function () {this.plugin.del();this.dom.parentNode.removeChild(this.dom);
};

再接下来是编写各个状态类的实现:

var StateFactory = (function () {var State = function () {};State.prototype.clickHandler1 = function () {throw new Error("子类必须重写父类的 clickHandler1 方法");};State.prototype.clickHandler2 = function () {throw new Error("子类必须重写父类的 clickHandler2 方法");};return function (param) {var F = function (uploadObj) {this.uploadObj = uploadObj;};F.prototype = new State();for (var i in param) {F.prototype[i] = param[i];}return F;};
})();var SignState = StateFactory({clickHandler1: function () {console.log("扫描中,点击无效...");},clickHandler2: function () {console.log("文件正在上传中,不能删除");},
});var UploadingState = StateFactory({clickHandler1: function () {this.uploadObj.pause();},clickHandler2: function () {console.log("文件正在上传中,不能删除");},
});var PauseState = StateFactory({clickHandler1: function () {this.uploadObj.uploading();},clickHandler2: function () {this.uploadObj.del();},
});var DoneState = StateFactory({clickHandler1: function () {console.log("文件已完成上传, 点击无效");},clickHandler2: function () {this.uploadObj.del();},
});var ErrorState = StateFactory({clickHandler1: function () {console.log("文件上传失败, 点击无效");},clickHandler2: function () {this.uploadObj.del();},
});

测试一下:

var uploadObj = new Upload("AAAAAAAAA");
uploadObj.init();
window.external.upload = function (state) {// 插件调用 JavaScript 的方法uploadObj[state]();
};
window.external.upload("sign"); // 文件开始扫描
setTimeout(function () {window.external.upload("uploading"); // 1 秒后开始上传
}, 1000);
setTimeout(function () {window.external.upload("done"); // 5 秒后上传完成
}, 5000);

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/241814.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【贪心】单源最短路径Python实现

文章目录 [toc]问题描述Dijkstra算法Dijkstra算法应用示例时间复杂性Python实现 个人主页&#xff1a;丷从心 系列专栏&#xff1a;贪心算法 问题描述 给定一个带权有向图 G ( V , E ) G (V , E) G(V,E)&#xff0c;其中每条边的权是非负实数&#xff0c;给定 V V V中的一个…

指南:在App Store Connect上编辑多个用户的访问权限

作为一名编程新手&#xff0c;在App Store Connect中管理用户权限可能初听起来有些复杂&#xff0c;但实际上它是一个相对直接的过程。这里是一个步骤清晰的指南来帮助您在App Store Connect上编辑多个用户的访问权限。 App Store Connect 简介 在开始之前&#xff0c;让我们…

Openwrt AP 发射 WiFi 信号

问题 想一次把 OpenWrt 路由器 wifi 问题给解决&#xff0c;完全取代路由器。 使用 倍控的 N5105 设备&#xff0c;有 mPCIe 接口&#xff0c;使用了 intel AX200 无线网卡&#xff0c;支持 2.4G 与 5G。 设置步骤 OpenWrt 镜像 第一次使用的镜像不支持 wifi&#xff0c;在…

“抓取再吸取的连续操作学习”研究工作发表于IEEE Trans. on Robotics:仿人手的柔性抓取,超人手的指背吸取!

长期以来&#xff0c;抓取一直被认为是机器人操作中一项重要而实际的任务。然而&#xff0c;实现对不同物体的稳健和有效的抓取具有挑战性&#xff0c;因为它涉及夹具设计、感知、控制和学习等。最近基于学习的方法在抓取各种新物体方面表现出优异的性能。然而&#xff0c;这些…

【C++11特性篇】新的类功能解读:新增加的[移动构造函数/移动赋值运算符重载]

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Linux》…

使用 Elasticsearch 检测抄袭 (二)

我在在之前的文章 “使用 Elasticsearch 检测抄袭 &#xff08;一&#xff09;” 介绍了如何检文章抄袭。这个在许多的实际使用中非常有意义。我在 CSDN 上的文章也经常被人引用或者抄袭。有的人甚至也不用指明出处。这对文章的作者来说是很不公平的。文章介绍的内容针对很多的…

Github 2023-12-24 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2023-12-24统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目5Jupyter Notebook项目2C项目1C项目1Go项目1Java项目1JavaScript项目1Ruby项目1 Serverless Frame…

双向长短期记忆网络(Bi-LSTM)-多输入回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码展示&#xff1a; 四、完整代码下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编…

vscode配置用户代码片段

1.左下角打开设置 2.新建全局代码片段 3.输入名字&#xff0c;名字随意不过最好语意化 4.创建模版 这里的模版为vue2常用代码片段&#xff0c;稍后会持续更新。 {"Print to console": {"prefix": "v2", //页面使用时名称"body":…

LeNet网络分析与demo实例

参考自 up主的b站链接&#xff1a;霹雳吧啦Wz的个人空间-霹雳吧啦Wz个人主页-哔哩哔哩视频这位大佬的博客 Fun_机器学习,pytorch图像分类,工具箱-CSDN博客 网络分析&#xff1a; 最好是把这个图像和代码对着来看然后进行分析的时候比较快 # 使用torch.nn包来构建神经网络. im…

Go 泛型之类型参数

Go 泛型之类型参数 文章目录 Go 泛型之类型参数一、Go 的泛型与其他主流编程语言的泛型差异二、返回切片中值最大的元素三、类型参数&#xff08;type parameters&#xff09;四、泛型函数3.1 泛型函数的结构3.2 调用泛型函数3.3 泛型函数实例化&#xff08;instantiation&…

WARNING: HADOOP_SECURE_DN_USER has been replaced by HDFS_DATANODE_SECURE_USER.

Hadoop启动时警告&#xff0c;但不影响使用&#xff0c;强迫症的我还是决定寻找解决办法 WARNING: HADOOP_SECURE_DN_USER has been replaced by HDFS_DATANODE_SECURE_USER. Using value of HADOOP_SECURE_DN_USER.原因是Hadoop安装配置于root用户下&#xff0c;对文件需要进…

案例144:基于微信小程序的自修室预约系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

Spring中的上下文工具你写的可能有bug

文章目录 前言功能第一种&#xff1a;ApplicationContext第二种方式&#xff1a;ApplicationContextAware第三种&#xff1a;BeanFactoryPostProcessor 源码第一种第二种第三种 前言 本篇是针对如何写一个比较好的spring工具的一个探讨。 功能 下面三种方式&#xff0c;你觉…

Odoo16 实用功能之Form视图详解(表单视图)

目录 1、什么是Form视图 2、Form视图的结构 3、源码示例 1、什么是Form视图 Form视图是用于查看和编辑数据库记录的界面。每个数据库模型在Odoo中都有一个Form视图&#xff0c;用于显示该模型的数据。Form视图提供了一个可编辑的界面&#xff0c;允许用户查看和修改数据库记…

[python]用python实现对arxml文件的操作

目录 关键词平台说明一、背景二、方法2.1 库2.2 code 关键词 python、excel、DBC、openpyxl 平台说明 项目Valuepython版本3.6 一、背景 有时候需要批量处理arxml文件(ARXML 文件符合 AUTOSAR 4.0 标准)&#xff0c;但是工作量太大&#xff0c;阔以考虑用python。 二、方…

最新版 JESD79-5B,2022年,JEDEC 内存SDRAM规范

本标准定义了DDR5 SDRAM规范&#xff0c;包括特性、功能、交流和直流特性、封装以及球/信号分配。本标准旨在为x4、x8和x16 DDR5 SDRAM设备定义符合JEDEC标准的8 Gb至32 Gb的最低要求。该标准是基于DDR4标准&#xff08;JESD79-4&#xff09;和DDR、DDR2、DDR3和LPDDR4标准的一…

智能优化算法应用:基于金枪鱼群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于金枪鱼群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于金枪鱼群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.金枪鱼群算法4.实验参数设定5.算法结果6.…

1856_emacs_calc使用介绍与故事

Grey 全部学习内容汇总&#xff1a; GitHub - GreyZhang/g_org: my learning trip for org-mode 1856_emacs_calc使用介绍与故事 calc是emacs内置的一个计算器&#xff0c;可以提供多种计算表达方式并且可以支持org-mode中的表格功能。 主题由来介绍 我是因为想要了解org-…

采草(动态规划)

先说说我的思路吧 下面是部分聊天记录 赤坂 龍之介 2023/12/22 11:06:04 就像我之前说的那样&#xff0c;我把每一个药草的价值除以时间&#xff0c;得出了新的价值评估标准&#xff1a;采摘这个药草时&#xff0c;每分钟的价值 赤坂 龍之介 2023/12/22 11:07:00 然后排…