当前位置: 首页 > news >正文

JS设计模式之状态模式:优雅地管理应用中产生的不同状态

image.png

一. 前言

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

简单来说,状态模式将每个状态封装成一个单独的类,并将状态间的转换逻辑封装在一个上下文对象中。通过这种方式,我们可以根据当前状态的不同,调用不同状态类中的方法来执行相应的逻辑。这种分离状态和行为的做法使得代码更加灵活和可扩展,并且便于我们理解和维护。

在本篇文章中,我们将探讨 JavaScript 状态模式的实现和应用,学习如何使用状态模式来避免 if-else 语句的嵌套,简化复杂状态的管理,并提高代码的可维护性。

那么接下来就让我们一步步了解如何在现有代码中引入状态模式,并考虑在实际情况中使用状态模式解决常见问题。

二. 什么是状态模式

JavaScript 状态模式是一种行为设计模式,用于通过将对象的行为和状态进行解耦,使得对象能够在不同的状态下具有不同的行为。它允许一个对象在其内部状态改变时更改其行为,而无需改变对象本身的结构。

在状态模式中,对象的行为取决于其当前状态。通过将状态封装为独立的类或对象,状态模式使得状态的变化可以独立于对象本身的变化。使用状态模式,可以将复杂的、分散的条件语句转化为状态类之间的切换,提高代码的可读性和可维护性。

状态模式通常由以下几个角色组成:

  1. 上下文环境Context):环境是拥有状态的对象,它维护一个对当前状态对象的引用,并在状态发生变化时更新自身的行为。

  2. 抽象状态State):抽象状态定义了一个接口,用于封装对象不同状态所对应的行为。

  3. 具体状态Concrete State):具体状态实现了抽象状态定义的接口,并定义了在该状态下对象的具体行为。

image.png

使用 JavaScript 状态模式可以提供以下优点:

  1. 结构清晰:将状态和行为分离,使代码结构更加清晰,易于理解和维护。

  2. 可扩展性:添加新的状态类简单,不需要修改其他代码,符合开闭原则。

  3. 低耦合:状态模式将状态的切换逻辑放在状态类中,状态之间可以独立变化,降低对象之间的耦合度。

  4. 简化条件判断:状态模式将复杂的条件语句转化为状态类之间的切换,使代码更简洁和可读。

总之,JavaScript 状态模式是一种有效的设计模式,可以帮助开发人员管理对象的状态和行为,提高代码的可维护性和可扩展性。

三. 实现方式

在 JavaScript 中,状态模式可以通过以下几个步骤来实现:

  1. 定义状态接口State Interface):在 JavaScript 中,我们可以使用类或对象字面量来定义状态接口。状态接口定义了状态类的共同方法,每个具体状态类都必须实现这些方法。

// 状态接口
class StateInterface {handleAction() {// 具体状态的行为逻辑}
}// 或者使用对象字面量
const stateInterface = {handleAction() {// 具体状态的行为逻辑},
};
  1. 实现具体状态类Concrete State Class):具体状态类实现状态接口,并定义每个具体状态的行为逻辑。

// 具体状态类
class ConcreteStateA extends StateInterface {handleAction() {// 具体状态A的行为逻辑}
}class ConcreteStateB extends StateInterface {handleAction() {// 具体状态B的行为逻辑}
}// 或者使用对象字面量
const concreteStateA = {handleAction() {// 具体状态A的行为逻辑},
};const concreteStateB = {handleAction() {// 具体状态B的行为逻辑},
};
  1. 定义上下文类Context Class):上下文类包含状态对象,并提供接口供客户端代码调用。上下文类将具体的状态转换逻辑封装在内部,通常会通过改变当前状态来触发不同的行为。

class Context {constructor() {this.state = null; // 当前状态}setState(state) {this.state = state;}handleAction() {this.state.handleAction();}
}
  1. 使用状态模式:在实际应用中,我们可以通过创建具体的状态对象,并将它们赋值给上下文对象来使用状态模式。

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,它包含了订单可能的操作方法。

然后,我们实现了具体的订单状态类 NewOrderStatePaidOrderStateShippedOrderStateCancelledOrderState,每个状态都覆盖了可能的操作,并在状态切换时设置相应的下一个状态。

最后,我们定义了订单管理类 Order,它包含了订单的当前状态,并提供了一些操作方法用于调用当前状态的相应操作。

通过使用状态模式,我们可以根据不同的订单状态触发相应的行为,如取消订单、支付订单、发货等。同时,我们可以轻松地添加新的状态类,并定义它们所需的行为逻辑,这使得代码结构清晰,并且易于扩展和维护。

五. 总结

JavaScript 状态模式是一种非常有用和强大的设计模式,它可以帮助开发人员管理对象的不同状态和相应的行为。通过将状态和行为分离,状态模式可以提高代码的可扩展性和复用性。

一般来说,状态模式具有许多优点,它可以使代码结构更加清晰易于理解和维护。通过将分散的、冗长的条件语句转换为状态类之间的切换,代码变得更加简洁和可读。此外,状态模式支持系统的扩展性,遵循开闭原则,方便添加新的状态类和行为。

然而,状态模式也有一些缺点。引入状态模式后,可能会增加类和对象的数量,从而增加代码的复杂度理解难度。在状态转换较为简单的场景中,引入状态模式可能并不切实际,会增加系统复杂性。

在实际应用中,要权衡状态模式的优缺点,根据具体的场景和需求进行选择。当对象有多个状态且状态之间存在复杂的转换逻辑时,状态模式是一个非常好的选择。

相关文章:

JS设计模式之状态模式:优雅地管理应用中产生的不同状态

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

C语言系列4——指针与数组(1)

我们开始C语言的指针与数组 这部分开始进阶了,得反复学习 在开始正题之前,写说一下我们都知道当写一个函数的时候需要进行传参,当实参传递给形参的时候,形参是有独立空间的,那么数组传参又是怎么样的呢,我…...

JS网页设计案例

下面是一个简单的 JavaScript 网页设计案例&#xff0c;展示了如何使用 HTML、CSS 和 JavaScript 创建一个动态的网页。 案例&#xff1a;简单的待办事项列表 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》&#xff0c;下表描述了GIC V3支持的INTID(硬件中断号)的范围。 SGI (Software Generated Interrupt)&#xff1a;软…...

常用性能优化方法

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

上海我店:创新模式引领本地生活新风尚

近年来&#xff0c;一个名为“上海我店”的新兴平台在网络空间中迅速崛起&#xff0c;其公布的业绩令人瞩目——在短短三年内&#xff0c;交易流水已跨越百亿大关&#xff0c;并在最近一个月内迎来了近百万的新增注册用户。这一强劲的增长势头&#xff0c;无疑吸引了众多商家和…...

【微服务】前端微服务qiankun 2.x主子应用通信代码片段

主应用代码 主应用工程里面源代码新建qiankun/index.js&#xff0c;通信代码如下&#xff1a; import { initGlobalState } from "qiankun"; import store from /store// 主应用与微应用数据通信 const state {subappClassName: // 设置子应用打包根的class类名 …...

高级java每日一道面试题-2024年9月30日-算法篇-LRU是什么?如何实现?

如果有遗漏,评论区告诉我进行补充 面试官: LRU是什么?如何实现? 我回答: LRU&#xff08;Least Recently Used&#xff09;是一种常用的缓存淘汰策略&#xff0c;用于在缓存满时决定哪些数据应该被移除。LRU算法的基本思想是&#xff1a;当缓存达到其容量上限时&#xff0…...

CSS选择器的全面解析与实战应用

CSS选择器的全面解析与实战应用 一、基本选择器1.1 通配符选择器&#xff08;*&#xff09;2.标签选择器&#xff08;div&#xff09;1.3 类名选择器&#xff08;.class&#xff09;4. id选择器&#xff08;#id&#xff09; 二、 属性选择器&#xff08;attr&#xff09;三、伪…...

vue3自动暴露element-plus组件的ref

自动暴露子组件的方法&#xff0c;注意在TS下&#xff0c;需要自己声明类型&#xff0c;我这里全用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版本下的安装及配置 通过源来安装&#xff0c;此次安装的版本为 redis 5.0 的&#xff0c;要通过其他源进行安装&#xff0c;首先安装 scl 源 yum install centos-release-scl-rh再安…...

Python 入门教程(4)数据类型 | 4.7、元组

文章目录 一、元组1、定义2、创建3、访问元组元素4、遍历元组5、 前言&#xff1a; 在Python编程中&#xff0c;元组&#xff08;tuple&#xff09;是一种内置的数据结构&#xff0c;它提供了一种存储多个项目&#xff08;元素&#xff09;的方式&#xff0c;这些项目可以是不同…...

Temu正在吸引越来越多的亚马逊卖家,这个市场Temu蝉联下载榜首

近年来&#xff0c;全球电商市场竞争愈发激烈&#xff0c;各大平台纷纷使出浑身解数&#xff0c;以期在激烈的市场竞争中脱颖而出。 一个来自中国的新兴电商平台——Temu&#xff0c;凭借其独特的市场策略和迅猛的发展势头&#xff0c;正在吸引越来越多的亚马逊卖家。Temu为美国…...

设计原则模式概览

前言 架构设计是软件系统稳定的核心因素&#xff0c;也是程序员晋级架构师的核心因素&#xff0c;建议日常开发过程中针对设计进行深挖与思考 核心 分清楚哪些是稳定的&#xff0c;哪些是变化的&#xff08;一定有稳定跟变化的成分&#xff09;&#xff1b; 捋清楚哪些是类设计…...

高级主题:接口性能测试与压力测试

在现代软件开发中&#xff0c;确保接口的性能和稳定性是非常重要的。随着用户数量的增加&#xff0c;接口需要能够承受高并发请求&#xff0c;从而保证良好的用户体验。本篇文章将介绍如何使用 Python 工具 Locust 进行接口性能测试和压力测试&#xff0c;分析测试结果&#xf…...

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…...

如何修复变砖的手机并恢复丢失的数据

您可能之前听说过“变砖”&#xff0c;但您知道什么是变砖手机吗&#xff1f;正如许多论坛中经常提出的问题一样&#xff0c;我如何知道我的手机是否变砖了&#xff1f;好吧&#xff0c;手机变砖主要有两种类型&#xff0c;即软件变砖和硬变砖。软变砖手机意味着重启后您仍然可…...

服务器使用了代理ip,遇到流量攻击,会对服务器有影响吗

当服务器使用代理IP并遭遇流量攻击&#xff08;如DDoS攻击&#xff09;时&#xff0c;仍然会对服务器产生影响。以下是关于这种情况的一些详细分析&#xff1a; 1. 流量攻击的性质 流量攻击的目的是通过发送大量请求来耗尽目标服务器的资源或带宽&#xff0c;导致服务中断或不…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案

在移动互联网营销竞争白热化的当下&#xff0c;推客小程序系统凭借其裂变传播、精准营销等特性&#xff0c;成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径&#xff0c;助力开发者打造具有市场竞争力的营销工具。​ 一、系统核心功能架构&…...

什么是VR全景技术

VR全景技术&#xff0c;全称为虚拟现实全景技术&#xff0c;是通过计算机图像模拟生成三维空间中的虚拟世界&#xff0c;使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验&#xff0c;结合图文、3D、音视频等多媒体元素…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…...

2.3 物理层设备

在这个视频中&#xff0c;我们要学习工作在物理层的两种网络设备&#xff0c;分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间&#xff0c;需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质&#xff0c;假设A节点要给…...