JS设计模式之状态模式:优雅地管理应用中产生的不同状态
一. 前言
在过去,我们经常使用条件语句(if-else
语句)来处理应用程序中的不同状态。然而,这种方式往往会让代码变得冗长、难以维护,并可能引入潜在的 bug。而状态模式则提供了一种更加结构化和可扩展的方法来处理状态转换。
简单来说,状态模式将每个状态封装成一个单独的类,并将状态间的转换逻辑封装在一个上下文对象中。通过这种方式,我们可以根据当前状态的不同,调用不同状态类中的方法来执行相应的逻辑。这种分离状态和行为的做法使得代码更加灵活和可扩展,并且便于我们理解和维护。
在本篇文章中,我们将探讨 JavaScript 状态模式的实现和应用,学习如何使用状态模式来避免 if-else
语句的嵌套,简化复杂状态的管理,并提高代码的可维护性。
那么接下来就让我们一步步了解如何在现有代码中引入状态模式,并考虑在实际情况中使用状态模式解决常见问题。
二. 什么是状态模式
JavaScript 状态模式是一种行为设计模式,用于通过将对象的行为和状态进行解耦,使得对象能够在不同的状态下具有不同的行为。它允许一个对象在其内部状态改变时更改其行为,而无需改变对象本身的结构。
在状态模式中,对象的行为取决于其当前状态。通过将状态封装为独立的类或对象,状态模式使得状态的变化可以独立于对象本身的变化。使用状态模式,可以将复杂的、分散的条件语句转化为状态类之间的切换,提高代码的可读性和可维护性。
状态模式通常由以下几个角色组成:
-
上下文环境(
Context
):环境是拥有状态的对象,它维护一个对当前状态对象的引用,并在状态发生变化时更新自身的行为。 -
抽象状态(
State
):抽象状态定义了一个接口,用于封装对象不同状态所对应的行为。 -
具体状态(
Concrete State
):具体状态实现了抽象状态定义的接口,并定义了在该状态下对象的具体行为。
使用 JavaScript 状态模式可以提供以下优点:
-
结构清晰:将状态和行为分离,使代码结构更加清晰,易于理解和维护。
-
可扩展性:添加新的状态类简单,不需要修改其他代码,符合开闭原则。
-
低耦合:状态模式将状态的切换逻辑放在状态类中,状态之间可以独立变化,降低对象之间的耦合度。
-
简化条件判断:状态模式将复杂的条件语句转化为状态类之间的切换,使代码更简洁和可读。
总之,JavaScript 状态模式是一种有效的设计模式,可以帮助开发人员管理对象的状态和行为,提高代码的可维护性和可扩展性。
三. 实现方式
在 JavaScript 中,状态模式可以通过以下几个步骤来实现:
-
定义状态接口(
State Interface
):在 JavaScript 中,我们可以使用类或对象字面量来定义状态接口。状态接口定义了状态类的共同方法,每个具体状态类都必须实现这些方法。
// 状态接口
class StateInterface {handleAction() {// 具体状态的行为逻辑}
}// 或者使用对象字面量
const stateInterface = {handleAction() {// 具体状态的行为逻辑},
};
-
实现具体状态类(
Concrete State Class
):具体状态类实现状态接口,并定义每个具体状态的行为逻辑。
// 具体状态类
class ConcreteStateA extends StateInterface {handleAction() {// 具体状态A的行为逻辑}
}class ConcreteStateB extends StateInterface {handleAction() {// 具体状态B的行为逻辑}
}// 或者使用对象字面量
const concreteStateA = {handleAction() {// 具体状态A的行为逻辑},
};const concreteStateB = {handleAction() {// 具体状态B的行为逻辑},
};
-
定义上下文类(
Context Class
):上下文类包含状态对象,并提供接口供客户端代码调用。上下文类将具体的状态转换逻辑封装在内部,通常会通过改变当前状态来触发不同的行为。
class Context {constructor() {this.state = null; // 当前状态}setState(state) {this.state = state;}handleAction() {this.state.handleAction();}
}
-
使用状态模式:在实际应用中,我们可以通过创建具体的状态对象,并将它们赋值给上下文对象来使用状态模式。
const context = new Context();// 设置初始状态
context.setState(new ConcreteStateA());// 调用上下文对象的方法进行具体操作
context.handleAction(); // 根据当前状态,执行对应的行为// 状态切换
context.setState(new ConcreteStateB());
context.handleAction(); // 根据当前状态,执行对应的行为
通过以上步骤,我们可以实现 JavaScript 中的状态模式。通过封装每个具体状态为单独的类,并在上下文对象中管理状态的切换,我们可以有效地组织和管理应用程序的不同状态。
这种结构化的设计模式提高了代码的可维护性和可扩展性,同时也增加了代码的可读性和清晰度。
四. 应用场景
举一个最常见的例子,就是电商网站的订单管理,一个订单可能经历多个不同的状态,如待付款、待发货、运输中、已完成等。状态模式可以帮助我们管理和切换这些不同的订单状态,从而处理相应的逻辑。
通过一个简单的代码示例来说明这个场景:
// 定义订单状态接口
class OrderState {// 将订单状态作为参数传入,以便在不同状态下执行相应的行为constructor(order) {this.order = order;}cancel() {throw new Error("该状态下不可取消订单");}pay() {throw new Error("该状态下不可支付订单");}ship() {throw new Error("该状态下不可发货");}// 其他可能的订单操作...
}// 具体订单状态类
class NewOrderState extends OrderState {cancel() {console.log("订单已取消");this.order.setState(new CancelledOrderState(this.order));}pay() {console.log("订单已支付");this.order.setState(new PaidOrderState(this.order));}ship() {console.log("订单未支付,无法发货");}
}class PaidOrderState extends OrderState {cancel() {console.log("订单已取消");this.order.setState(new CancelledOrderState(this.order));}pay() {console.log("订单已支付,请勿重复支付");}ship() {console.log("订单已发货");this.order.setState(new ShippedOrderState(this.order));}
}class ShippedOrderState extends OrderState {cancel() {console.log("订单已发货,无法取消");}pay() {console.log("订单已支付,请勿重复支付");}ship() {console.log("订单已发货,请勿重复发货");}// 其他可能的订单操作...
}class CancelledOrderState extends OrderState {cancel() {console.log("订单已取消,请勿重复取消");}// 其他可能的订单操作...
}// 订单管理类
class Order {constructor() {// 设置初始状态为新订单状态this.state = new NewOrderState(this);}setState(state) {this.state = state;}cancel() {this.state.cancel();}pay() {this.state.pay();}ship() {this.state.ship();}// 其他可能的订单操作...
}
使用示例如下所示:
const order = new Order();order.cancel(); // 输出:"该状态下不可取消订单"order.pay(); // 输出:"订单已支付"order.pay(); // 输出:"订单已支付,请勿重复支付"order.ship(); // 输出:"订单已发货"order.cancel(); // 输出:"订单已发货,无法取消"
在上面的代码示例中,我们首先定义了一个订单状态接口 OrderState
,它包含了订单可能的操作方法。
然后,我们实现了具体的订单状态类 NewOrderState
、PaidOrderState
、ShippedOrderState
和 CancelledOrderState
,每个状态都覆盖了可能的操作,并在状态切换时设置相应的下一个状态。
最后,我们定义了订单管理类 Order
,它包含了订单的当前状态,并提供了一些操作方法用于调用当前状态的相应操作。
通过使用状态模式,我们可以根据不同的订单状态触发相应的行为,如取消订单、支付订单、发货等。同时,我们可以轻松地添加新的状态类,并定义它们所需的行为逻辑,这使得代码结构清晰,并且易于扩展和维护。
五. 总结
JavaScript 状态模式是一种非常有用和强大的设计模式,它可以帮助开发人员管理对象的不同状态和相应的行为。通过将状态和行为分离,状态模式可以提高代码的可扩展性和复用性。
一般来说,状态模式具有许多优点,它可以使代码结构更加清晰,易于理解和维护。通过将分散的、冗长的条件语句转换为状态类之间的切换,代码变得更加简洁和可读。此外,状态模式支持系统的扩展性,遵循开闭原则,方便添加新的状态类和行为。
然而,状态模式也有一些缺点。引入状态模式后,可能会增加类和对象的数量,从而增加代码的复杂度和理解难度。在状态转换较为简单的场景中,引入状态模式可能并不切实际,会增加系统复杂性。
在实际应用中,要权衡状态模式的优缺点,根据具体的场景和需求进行选择。当对象有多个状态且状态之间存在复杂的转换逻辑时,状态模式是一个非常好的选择。
相关文章:

JS设计模式之状态模式:优雅地管理应用中产生的不同状态
一. 前言 在过去,我们经常使用条件语句(if-else 语句)来处理应用程序中的不同状态。然而,这种方式往往会让代码变得冗长、难以维护,并可能引入潜在的 bug。而状态模式则提供了一种更加结构化和可扩展的方法来处理状态…...

C语言系列4——指针与数组(1)
我们开始C语言的指针与数组 这部分开始进阶了,得反复学习 在开始正题之前,写说一下我们都知道当写一个函数的时候需要进行传参,当实参传递给形参的时候,形参是有独立空间的,那么数组传参又是怎么样的呢,我…...
JS网页设计案例
下面是一个简单的 JavaScript 网页设计案例,展示了如何使用 HTML、CSS 和 JavaScript 创建一个动态的网页。 案例:简单的待办事项列表 1. HTML 部分 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8…...

4.2.1 通过DTS传递物理中断号给Linux
点击查看系列文章 》 Interrupt Pipeline系列文章大纲-CSDN博客 4.2.1 通过DTS传递物理中断号给Linux 参考《GICv3_Software_Overview_Official_Release_B》,下表描述了GIC V3支持的INTID(硬件中断号)的范围。 SGI (Software Generated Interrupt):软…...

常用性能优化方法
在一个Java项目中进行性能优化是至关重要的。性能优化能够提高项目的效率和响应速度,提升用户体验,并且可以节省服务器资源和成本。 首先,性能优化可以确保项目的高效运行。当项目在运行时,性能问题可能会导致应用程序变慢、响应时…...

上海我店:创新模式引领本地生活新风尚
近年来,一个名为“上海我店”的新兴平台在网络空间中迅速崛起,其公布的业绩令人瞩目——在短短三年内,交易流水已跨越百亿大关,并在最近一个月内迎来了近百万的新增注册用户。这一强劲的增长势头,无疑吸引了众多商家和…...
【微服务】前端微服务qiankun 2.x主子应用通信代码片段
主应用代码 主应用工程里面源代码新建qiankun/index.js,通信代码如下: import { initGlobalState } from "qiankun"; import store from /store// 主应用与微应用数据通信 const state {subappClassName: // 设置子应用打包根的class类名 …...
高级java每日一道面试题-2024年9月30日-算法篇-LRU是什么?如何实现?
如果有遗漏,评论区告诉我进行补充 面试官: LRU是什么?如何实现? 我回答: LRU(Least Recently Used)是一种常用的缓存淘汰策略,用于在缓存满时决定哪些数据应该被移除。LRU算法的基本思想是:当缓存达到其容量上限时࿰…...

CSS选择器的全面解析与实战应用
CSS选择器的全面解析与实战应用 一、基本选择器1.1 通配符选择器(*)2.标签选择器(div)1.3 类名选择器(.class)4. id选择器(#id) 二、 属性选择器(attr)三、伪…...
vue3自动暴露element-plus组件的ref
自动暴露子组件的方法,注意在TS下,需要自己声明类型,我这里全用any代替了 <template><el-button click"getFocus">获得焦点</el-button><com ref"comRef" /> </template><script setup…...
龙芯+FreeRTOS+LVGL实战笔记(新)——10蜂鸣器嘀嘀嘀
本专栏是笔者另一个专栏《龙芯+RT-Thread+LVGL实战笔记》的姊妹篇,主要的区别在于实时操作系统的不同,章节的安排和任务的推进保持一致,并对源码做了完善与优化,各位可以先到本人主页下去浏览另一专栏的博客列表(目前已撰写36篇,图1所示),再决定是否订阅。此外,也可以…...

微信小程序-数据模型与动态赋值
首先新建一个小程序项目. 这边有创建基础项目的流程:从0新建一个微信小程序实现一个简单跳转_小白开发小程序源代码-CSDN博客 一共两步: 1.建立页面的 数据模型 和 默认赋值: 默认赋值: 2.接收输入框的新文案,动态替换上面的文案展示 //文件 testUI.js增加方法:onInputChan…...

【Redis】Linux下安装配置及通过C++访问Redis
文章目录 一、Linux Centos 7.0版本下的安装及配置二、通过C访问Redis 一、Linux Centos 7.0版本下的安装及配置 通过源来安装,此次安装的版本为 redis 5.0 的,要通过其他源进行安装,首先安装 scl 源 yum install centos-release-scl-rh再安…...
Python 入门教程(4)数据类型 | 4.7、元组
文章目录 一、元组1、定义2、创建3、访问元组元素4、遍历元组5、 前言: 在Python编程中,元组(tuple)是一种内置的数据结构,它提供了一种存储多个项目(元素)的方式,这些项目可以是不同…...

Temu正在吸引越来越多的亚马逊卖家,这个市场Temu蝉联下载榜首
近年来,全球电商市场竞争愈发激烈,各大平台纷纷使出浑身解数,以期在激烈的市场竞争中脱颖而出。 一个来自中国的新兴电商平台——Temu,凭借其独特的市场策略和迅猛的发展势头,正在吸引越来越多的亚马逊卖家。Temu为美国…...
设计原则模式概览
前言 架构设计是软件系统稳定的核心因素,也是程序员晋级架构师的核心因素,建议日常开发过程中针对设计进行深挖与思考 核心 分清楚哪些是稳定的,哪些是变化的(一定有稳定跟变化的成分); 捋清楚哪些是类设计…...
高级主题:接口性能测试与压力测试
在现代软件开发中,确保接口的性能和稳定性是非常重要的。随着用户数量的增加,接口需要能够承受高并发请求,从而保证良好的用户体验。本篇文章将介绍如何使用 Python 工具 Locust 进行接口性能测试和压力测试,分析测试结果…...

python绘制图像
柱状图 import os# 输入想要存储图像的路径 os.chdir(D:)import matplotlib.pyplot as plt import numpy as np # 改变绘图风格 import seaborn as snssns.set(color_codesTrue)cell [gen7, xgspon, 3081GB, vettel, totalplay, other] pvalue [21, 20, 18, 13, 7, 34]width…...

如何修复变砖的手机并恢复丢失的数据
您可能之前听说过“变砖”,但您知道什么是变砖手机吗?正如许多论坛中经常提出的问题一样,我如何知道我的手机是否变砖了?好吧,手机变砖主要有两种类型,即软件变砖和硬变砖。软变砖手机意味着重启后您仍然可…...
服务器使用了代理ip,遇到流量攻击,会对服务器有影响吗
当服务器使用代理IP并遭遇流量攻击(如DDoS攻击)时,仍然会对服务器产生影响。以下是关于这种情况的一些详细分析: 1. 流量攻击的性质 流量攻击的目的是通过发送大量请求来耗尽目标服务器的资源或带宽,导致服务中断或不…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...

C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...