JavaScript设计模式 -- 状态模式

news/2025/2/21 9:49:34/

在软件开发中,很多对象的行为会随着其内部状态的变化而改变。如果将所有状态逻辑写在一个类中,代码不仅臃肿而且难以维护。**状态模式(State Pattern)**正是为了解决这个问题而设计的。通过将对象的状态封装成独立的状态类,并将状态相关的行为转移到这些状态类中,状态模式让对象在内部状态发生变化时自动切换行为,达到了将状态转换与行为实现分离的目的。

本文将详细介绍状态模式的核心思想、基本结构与优缺点,并通过多个示例展示状态模式在不同场景下的应用,包括交通信号灯、自动售货机、UI 按钮状态以及游戏角色状态等多个方面。

状态模式简介

状态模式是一种行为型设计模式,其主要思想是将对象在不同状态下的行为封装到不同的状态类中,并通过上下文(Context)对象维护当前状态。当上下文状态发生变化时,它会将请求委托给当前状态对象,从而使得对象的行为随着状态的改变而改变。

例如,一个交通信号灯对象,根据当前状态(红灯、绿灯、黄灯)的不同,会执行不同的操作;而一个自动售货机在“无硬币”、“有硬币”和“售出商品”等状态下,其响应行为也各不相同。状态模式通过分离状态逻辑,使得代码更易维护和扩展。


状态模式的结构与特点

主要角色:

  • 上下文(Context)
    维护一个对当前状态对象的引用,并委托状态对象处理请求。

  • 状态接口(State Interface)
    定义状态对象必须实现的方法,通常是与上下文相关的行为。

  • 具体状态(Concrete State)
    实现状态接口的类,每个类代表一种状态,并封装在该状态下的行为。

特点:

  • 状态封装:将与状态相关的行为封装到独立的状态类中,使得状态变化与行为实现解耦。
  • 行为动态切换:上下文对象通过持有状态对象的引用,根据内部状态的变化动态切换行为。
  • 易于扩展:增加新状态只需增加新的具体状态类,而无需修改上下文类代码。

JavaScript 实现示例

下面通过多个示例展示如何在 JavaScript 中使用状态模式

示例 1:交通信号灯

交通信号灯根据当前状态(红灯、绿灯、黄灯)的不同,执行不同的动作,并在状态之间循环切换。

// 定义状态接口及具体状态类
class TrafficLightState {constructor(context) {this.context = context;}handle() {throw new Error('子类必须实现 handle 方法');}
}class RedState extends TrafficLightState {handle() {console.log('红灯:停止');// 状态转换到绿灯this.context.setState(new GreenState(this.context));}
}class GreenState extends TrafficLightState {handle() {console.log('绿灯:通行');// 状态转换到黄灯this.context.setState(new YellowState(this.context));}
}class YellowState extends TrafficLightState {handle() {console.log('黄灯:注意');// 状态转换到红灯this.context.setState(new RedState(this.context));}
}// 上下文类:交通信号灯
class TrafficLight {constructor() {// 初始状态设为红灯this.state = new RedState(this);}setState(state) {this.state = state;}change() {this.state.handle();}
}// 客户端调用
const trafficLight = new TrafficLight();
trafficLight.change(); // 输出:红灯:停止 -> 状态切换为绿灯
trafficLight.change(); // 输出:绿灯:通行 -> 状态切换为黄灯
trafficLight.change(); // 输出:黄灯:注意 -> 状态切换为红灯

示例 2:自动售货机

自动售货机在不同状态下的行为不同,如“无硬币”、“有硬币”、“售出商品”状态。状态模式可以帮助我们将这些逻辑拆分为独立的状态类。

// 状态接口
class VendingState {constructor(machine) {this.machine = machine;}insertCoin() {throw new Error('insertCoin 方法未实现');}selectProduct() {throw new Error('selectProduct 方法未实现');}dispenseProduct() {throw new Error('dispenseProduct 方法未实现');}
}// 具体状态:无硬币状态
class NoCoinState extends VendingState {insertCoin() {console.log('硬币已投入');this.machine.setState(new HasCoinState(this.machine));}selectProduct() {console.log('请先投入硬币');}dispenseProduct() {console.log('请先投入硬币');}
}// 具体状态:有硬币状态
class HasCoinState extends VendingState {insertCoin() {console.log('已投入硬币,请勿重复投币');}selectProduct() {console.log('产品已选择');this.machine.setState(new SoldState(this.machine));}dispenseProduct() {console.log('请先选择产品');}
}// 具体状态:售出状态
class SoldState extends VendingState {insertCoin() {console.log('正在处理当前交易,请稍候');}selectProduct() {console.log('产品已选择,请稍候');}dispenseProduct() {console.log('产品已出货');// 交易完成后恢复为无硬币状态this.machine.setState(new NoCoinState(this.machine));}
}// 上下文类:自动售货机
class VendingMachine {constructor() {this.state = new NoCoinState(this);}setState(state) {this.state = state;}insertCoin() {this.state.insertCoin();}selectProduct() {this.state.selectProduct();}dispenseProduct() {this.state.dispenseProduct();}
}// 客户端调用
const vendingMachine = new VendingMachine();
vendingMachine.selectProduct(); // 输出:请先投入硬币
vendingMachine.insertCoin();      // 输出:硬币已投入
vendingMachine.insertCoin();      // 输出:已投入硬币,请勿重复投币
vendingMachine.selectProduct();   // 输出:产品已选择
vendingMachine.dispenseProduct(); // 输出:产品已出货

示例 3:UI 按钮状态切换

在前端 UI 中,一个按钮可能处于“启用”、“禁用”以及“加载中”等状态,不同状态下的按钮表现不同。状态模式可以使按钮行为与状态解耦。

// 状态接口
class ButtonState {constructor(button) {this.button = button;}click() {throw new Error('click 方法未实现');}render() {throw new Error('render 方法未实现');}
}// 具体状态:启用状态
class EnabledState extends ButtonState {click() {console.log('按钮被点击,开始加载数据...');this.button.setState(new LoadingState(this.button));}render() {console.log('渲染启用按钮');}
}// 具体状态:禁用状态
class DisabledState extends ButtonState {click() {console.log('按钮处于禁用状态,无法点击');}render() {console.log('渲染禁用按钮');}
}// 具体状态:加载中状态
class LoadingState extends ButtonState {click() {console.log('加载中,无法再次点击');}render() {console.log('渲染加载中按钮');}
}// 上下文类:UI 按钮
class Button {constructor() {// 默认状态为启用状态this.state = new EnabledState(this);}setState(state) {this.state = state;this.state.render();}click() {this.state.click();}
}// 客户端调用
const uiButton = new Button();
uiButton.click();           // 输出:按钮被点击,开始加载数据... 并切换到加载中状态
uiButton.click();           // 输出:加载中,无法再次点击
uiButton.setState(new DisabledState(uiButton));
uiButton.click();           // 输出:按钮处于禁用状态,无法点击

示例 4:游戏角色状态

在游戏中,一个角色可能有“空闲”、“奔跑”、“攻击”、“跳跃”等不同状态,每个状态下角色的行为和表现各不相同。利用状态模式,可以轻松管理角色状态及其转换。

// 状态接口
class CharacterState {constructor(character) {this.character = character;}handle() {throw new Error('handle 方法未实现');}
}// 具体状态:空闲状态
class IdleState extends CharacterState {handle() {console.log('角色处于空闲状态');// 根据条件可以切换到其他状态// this.character.setState(new RunningState(this.character));}
}// 具体状态:奔跑状态
class RunningState extends CharacterState {handle() {console.log('角色正在奔跑');// this.character.setState(new JumpingState(this.character));}
}// 具体状态:跳跃状态
class JumpingState extends CharacterState {handle() {console.log('角色正在跳跃');// 完成跳跃后恢复到空闲状态this.character.setState(new IdleState(this.character));}
}// 上下文类:游戏角色
class GameCharacter {constructor() {this.state = new IdleState(this);}setState(state) {this.state = state;}performAction() {this.state.handle();}
}// 客户端调用
const hero = new GameCharacter();
hero.performAction();            // 输出:角色处于空闲状态
hero.setState(new RunningState(hero));
hero.performAction();            // 输出:角色正在奔跑
hero.setState(new JumpingState(hero));
hero.performAction();            // 输出:角色正在跳跃,然后状态切换回空闲状态
hero.performAction();            // 输出:角色处于空闲状态

状态模式的优缺点

优点

  • 清晰的状态分离:将每个状态的行为封装在独立的状态类中,便于管理和扩展。
  • 简化上下文逻辑:上下文类只需维护当前状态,通过委托状态对象处理请求,避免了大量的条件判断。
  • 动态状态切换:在运行时可根据条件动态切换状态,灵活应对复杂业务逻辑。

缺点

  • 类数量增多:每种状态都需要单独的类,对于状态较多的系统可能会导致类的数量较多。
  • 状态转换逻辑分散:状态之间的转换逻辑可能会分布在不同状态类中,维护时需要特别注意状态之间的依赖关系。

总结

状态模式通过将对象的行为封装在各个状态类中,使得对象能够根据内部状态的变化自动调整行为,避免了大量条件判断和耦合代码。本文详细介绍了状态模式的基本概念、结构和优缺点,并通过交通信号灯、自动售货机、UI 按钮状态切换以及游戏角色状态等多个示例,展示了状态模式在不同场景下的应用。希望这些示例能帮助你在实际项目中更好地管理状态转换和行为变化,提高代码的灵活性与可维护性。

欢迎在评论区分享你的理解与实践经验!


http://www.ppmy.cn/news/1573850.html

相关文章

(萌新入门)如何从起步阶段开始学习STM32 —— 1如何迁移一个开发版的工程

目录 前言 如何迁移一个开发版的工程到其他单片机上? Step 1:明确你单片机的型号 STEP2 确定环境后移植代码 提示:正常而言,我们是使用的套模板建立工程 提示:笔者自身不使用这些模板,我是使用CubeMX快…

Netty入门详解

引言 Netty 是一个基于 Java 的高性能、异步事件驱动的网络应用框架,用于快速开发可维护的高性能网络服务器和客户端。它提供了一组丰富的 API,使得开发人员能够轻松地处理各种网络协议,如 TCP、UDP 等,并且支持多种编解码方式&a…

机器学习小项目之鸢尾花分类

项目目标: 使用机器学习算法(如 K-近邻算法)来对鸢尾花数据集进行分类。 1. 准备工作 首先,我们需要安装一些常用的机器学习库,如 scikit-learn 和 pandas。 pip install scikit-learn pandas matplotlib2. 导入必…

Nginx解决前端跨域问题

1. 理解 CORS 和同源策略 1.1 同源策略 同源策略是一种浏览器安全机制,用于阻止不同源(不同域名、协议或端口)的 Web 应用相互访问数据。它确保了 Web 应用的隔离性,防止恶意网站访问用户数据或执行不安全的操作。 同源策略下&…

Debian软件包重构

Explore projects GitLab 1. apt-get build-dep <pkg> ## 安装编译依赖包 2. apt source <pkg> ## 下载 <pkg> 包的源码 3. 创建 git &#xff0c;打补丁 4. dpkg-buildpackage -b -uc -us -d ## 重新打包编译 # 解压出包中的文件到 extract 目…

linux服务简介

Linux服务是在后台运行的程序或进程&#xff0c;通常称为守护进程&#xff08;daemon&#xff09;&#xff0c;用于提供系统或网络功能。它们随系统启动而自动运行&#xff0c;无需用户交互&#xff0c;常见的如网络服务、日志服务等。以下是Linux服务的详细介绍&#xff1a; …

ip is not allowed to connect to this Mysql server

本文介绍mysql8.4外部远程连接时报“ ip is not allowed to connect to this Mysql server”的处理过程。刚刚升级到mysql8.4版本的同学&#xff0c;一定会碰到此问题&#xff0c;原因是mysql8.4版本后&#xff0c;mysql修改了验证方式。 报错效果如下&#xff08;这里是用工具…

Windows 快速搭建C++开发环境,安装C++、CMake、QT、Visual Studio、Setup Factory

安装C 简介 Windows 版的 GCC 有三个选择&#xff1a; CygwinMinGWmingw-w64 Cygwin、MinGW 和 mingw-w64 都是在 Windows 操作系统上运行的工具集&#xff0c;用于在 Windows 环境下进行开发和编译。 Cygwin 是一个在 Windows 上运行的开源项目&#xff0c;旨在提供类Uni…