JS如何实现状态模式

答案:JavaScript中实现状态模式可通过封装不同状态行为于独立对象中,避免冗余条件判断。示例中MediaPlayer作为上下文持有当前状态引用,并将播放、暂停、停止操作委托给具体状态对象处理;每个状态类(如PlayingState、PausedState、StoppedState)实现对应行为并可改变上下文状态,从而实现行为随状态变化而变化,提升代码可维护性与扩展性。

js如何实现状态模式

在JavaScript中实现状态模式,核心在于让一个对象的行为在其内部状态改变时也随之改变,并且这些状态相关的行为被封装在独立的状态对象中。这样能有效避免大量的条件判断语句,让代码更清晰、更易于维护和扩展。

解决方案

实现状态模式,通常会涉及一个“上下文”(Context)对象和多个“具体状态”(Concrete State)对象。上下文对象持有当前状态的引用,并将请求委托给当前状态对象处理。每个具体状态对象则负责实现特定状态下的行为,并可以根据需要改变上下文的状态。

以下是一个简单的JavaScript实现示例,以一个媒体播放器为例,它有“播放中”、“暂停”和“停止”三种状态:

// 抽象状态或状态接口 (在JS中通常通过定义一组共同方法来模拟)class PlayerState {    play() { throw new Error("This method must be overridden!"); }    pause() { throw new Error("This method must be overridden!"); }    stop() { throw new Error("This method must be overridden!"); }}// 具体状态:播放中class PlayingState extends PlayerState {    constructor(player) {        super();        this.player = player;    }    play() {        console.log("已经在播放了,无需重复操作。");    }    pause() {        console.log("暂停播放。");        this.player.setState(this.player.pausedState);    }    stop() {        console.log("停止播放。");        this.player.setState(this.player.stoppedState);    }}// 具体状态:暂停class PausedState extends PlayerState {    constructor(player) {        super();        this.player = player;    }    play() {        console.log("恢复播放。");        this.player.setState(this.player.playingState);    }    pause() {        console.log("已经暂停了。");    }    stop() {        console.log("停止播放。");        this.player.setState(this.player.stoppedState);    }}// 具体状态:停止class StoppedState extends PlayerState {    constructor(player) {        super();        this.player = player;    }    play() {        console.log("开始播放。");        this.player.setState(this.player.playingState);    }    pause() {        console.log("当前已停止,无法暂停。");    }    stop() {        console.log("已经停止了。");    }}// 上下文:媒体播放器class MediaPlayer {    constructor() {        this.playingState = new PlayingState(this);        this.pausedState = new PausedState(this);        this.stoppedState = new StoppedState(this);        // 初始状态        this.currentState = this.stoppedState;        console.log("播放器初始化,当前状态:停止。");    }    setState(state) {        this.currentState = state;        // 可以在这里添加一些状态切换的日志或副作用        console.log(`状态已切换到:${state.constructor.name}`);    }    play() {        this.currentState.play();    }    pause() {        this.currentState.pause();    }    stop() {        this.currentState.stop();    }}// 使用示例const player = new MediaPlayer();player.play();   // 开始播放player.pause();  // 暂停player.play();   // 恢复播放player.stop();   // 停止player.pause();  // 无法暂停(已停止)player.stop();   // 已经停止

在这个例子里,

MediaPlayer

是上下文,它不直接处理播放、暂停、停止的逻辑,而是将这些行为委托给

currentState

。每个状态类(

PlayingState

,

PausedState

,

StoppedState

)封装了在该状态下,这些操作的具体行为,并且能够决定在特定操作后,播放器应该切换到哪个新状态。

为什么要在JavaScript中使用状态模式?

我个人觉得,最头疼的就是那些随着对象状态变化而变得臃肿不堪的条件判断。想象一下,一个复杂的订单系统,有“待支付”、“已支付”、“已发货”、“已取消”等状态,每个状态下,订单可以执行的操作(如修改地址、退款、确认收货)都可能不同。如果用大量的

if/else if

switch

语句来判断当前状态并执行对应逻辑,那代码很快就会变成一团乱麻,维护起来简直是噩梦。

状态模式的魅力就在于它能把这些散落在各处的、依赖于状态的逻辑,清晰地封装到各自独立的状态类中。这不仅让代码结构变得异常整洁,每个状态类只关心自己的行为和可能的转换,大大提升了可读性和可维护性。想新增一个状态?只需要添加一个新的状态类,并调整相关状态的转换逻辑,而无需修改大量现有代码。这简直是软件“开闭原则”的典范应用,对大型、复杂且状态变化频繁的系统来说,它能显著降低维护成本和引入新功能的风险。

状态模式与策略模式有何不同?

我常常会把这俩搞混,后来发现,关键在于那个“谁在变”。状态模式和策略模式确实有很多相似之处:它们都使用了组合(Composition)而非继承,都通过委托(Delegation)来改变对象的行为,并且都将算法或行为封装在独立的类中。但它们的核心意图和行为改变的驱动力是不同的。

状态模式中,是“上下文对象”的内部状态在改变,而上下文对象的行为也随之改变。你可以把状态对象看作是上下文对象在特定时刻的“人格”或“模式”。例如,我的播放器在“播放中”和“暂停”时,点击“播放”按钮的行为是完全不同的。这种行为的切换是内部状态驱动的,上下文对象会主动改变它当前持有的状态对象。

策略模式则不同,它关注的是“算法族”的封装。上下文对象(或客户端)会选择一个具体的策略来执行某个任务,但上下文对象本身的“状态”并没有改变。比如,一个排序器可以选择“冒泡排序”策略或“快速排序”策略来对数据进行排序。排序器本身还是排序器,只是它执行排序的方式变了,这种改变通常是由外部(客户端)来选择或配置的。

简而言之,状态模式是“我在什么状态下,就做什么事”,状态是内在的、动态变化的;策略模式是“我选择哪种方式来做这件事”,策略是可替换的、通常由外部选择的。

实现状态模式时常见的陷阱与考量?

说实话,刚开始用状态模式的时候,我确实踩过不少坑。最常见的就是,明明一个

if

就能搞定的事,非要硬套模式,结果代码反而更复杂了。不是所有状态相关的逻辑都非得用状态模式,对于只有两三个状态,且状态转换逻辑非常简单的场景,过度设计反而会增加不必要的复杂性和样板代码。这就像你为了钉个小图钉,非得搬出一套重型电钻一样,完全没必要。

另一个让我纠结的问题是:状态转换的逻辑,到底是应该放在上下文对象里,还是放在各个具体的状态对象里?在上面的播放器例子中,我把状态转换逻辑(

this.player.setState(...)

)放在了具体的状态对象内部。这样做的好处是,每个状态对象完全掌控了自己在接收到某个操作后,应该如何响应以及如何转换到下一个状态,这让状态的封装性变得非常好。但缺点是,状态对象需要持有上下文对象的引用,这可能会引入循环依赖,并且如果状态转换逻辑非常复杂,状态对象本身也会变得比较臃肿。

反之,如果把所有状态转换逻辑都集中在上下文对象中,上下文对象会变得相对复杂,因为它需要知道所有状态及其转换规则。这又回到了我们最初想避免的“大

switch

语句”问题。

我个人的经验是,对于大多数情况,让状态对象自己负责状态转换是更优雅的选择,它符合“单一职责原则”:每个状态对象只关心自己在当前状态下的行为以及如何根据操作进入下一个状态。但如果状态转换逻辑异常复杂,或者有大量的状态间共享的转换规则,那么在上下文对象中集中管理一部分转换逻辑也未尝不可,关键在于权衡和取舍,找到最适合当前场景的平衡点。

最后,调试也可能变得稍微复杂一些。因为行为被分散到多个状态对象中,当出现问题时,你可能需要追踪当前上下文处于哪个状态,以及这个状态是如何被改变的。因此,在实现时,适当地添加日志(比如示例中的

console.log

)来追踪状态变化,会非常有帮助。

以上就是JS如何实现状态模式的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1517823.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
TestCafe userVariables 获取不到元素问题的排查与解决
上一篇 2025年12月20日 11:02:25
什么是希尔排序?希尔排序的优势
下一篇 2025年12月20日 11:02:39

相关推荐

  • 表单数据验证与过滤的最佳实践

    我们需要重视表单数据的验证和过滤,以确保应用的安全性和数据的完整性。1) 结合使用客户端和服务器端验证,客户端提供即时反馈,服务器端确保数据安全。2) 验证不同类型的数据,如字符串、数字、日期,确保格式和业务逻辑正确。3) 处理错误时提供友好的错误信息,并防止泄露敏感信息。4) 使用适当的函数过滤数…

    2026年8月26日
    000
  • 怎么进入路由器设置界面_浏览器登录路由器管理后台教程

    进入路由器设置界面,说白了就是通过浏览器访问路由器的管理后台。一般情况下,在浏览器地址栏输入路由器背面贴纸上的地址(通常是192.168.1.1或192.168.0.1),然后输入用户名和密码就能进去了。但实际操作中,可能会遇到各种小问题,下面就详细说说。 解决方案 确认路由器已连接: 确保你的电脑…

    2026年8月26日
    000
  • Java中静态方法能重写吗 分析Java静态方法隐藏现象的本质

    Java中静态方法能重写吗 分析Java静态方法隐藏现象的本质Java中静态方法能重写吗 分析Java静态方法隐藏现象的本质Java中静态方法能重写吗 分析Java静态方法隐藏现象的本质Java中静态方法能重写吗 分析Java静态方法隐藏现象的本质

    java中的静态方法不能被重写,但可以被隐藏,这是因为在编译时根据引用类型决定调用哪个类的静态方法。1. 静态方法属于类而非实例,因此它们在编译时绑定;2. 子类定义与父类相同的静态方法会隐藏父类方法,而不是实现多态;3. 调用时依据引用类型而非对象实际类型,导致parent.printmessag…

    2026年8月26日 用户投稿
    200
  • 第三方登录(微信、QQ等)集成方法

    集成第三方登录是为了简化用户注册和登录流程,提高用户体验和安全性。具体步骤包括:1)了解oauth 2.0协议;2)在应用中添加第三方登录入口;3)处理授权码和访问令牌;4)获取用户信息;5)处理常见问题和优化性能。 集成第三方登录(如微信、QQ等)是现代应用开发中常见的需求。为什么要集成第三方登录…

    2026年8月26日
    100
  • 《幻想生活i》全球累计销量突破120万份

    《幻想生活i》全球累计销量突破120万份《幻想生活i》全球累计销量突破120万份《幻想生活i》全球累计销量突破120万份《幻想生活i》全球累计销量突破120万份

    level-5 宣布,《幻想生活i》全球销量已超过120万份,特别发布了纪念插图,并宣布switch2实体版将于8月7日正式发售。此外,拍照模式也将在本月内通过更新加入游戏内容。 以上就是《幻想生活i》全球累计销量突破120万份的详细内容,更多请关注创想鸟其它相关文章!

    2026年8月26日 用户投稿
    100
  • 如何用Swoole实现数据库连接池?

    需要数据库连接池是因为它能减少频繁创建和销毁数据库连接的性能开销。1) 数据库连接池预先创建连接供复用,2) 提高系统响应速度和稳定性,3) 降低数据库服务器负担。 在探索如何用Swoole实现数据库连接池之前,让我们先来回答一个关键问题:为什么需要数据库连接池? 数据库连接池的存在是为了解决频繁创…

    2026年8月26日
    000
  • 《旧日飞车》正式版年底登陆多平台 PS5/NS试玩版公开

    《旧日飞车》正式版年底登陆多平台 PS5/NS试玩版公开《旧日飞车》正式版年底登陆多平台 PS5/NS试玩版公开《旧日飞车》正式版年底登陆多平台 PS5/NS试玩版公开《旧日飞车》正式版年底登陆多平台 PS5/NS试玩版公开

    astrolabe games近日宣布,旗下ea阶段备受好评的复古赛车游戏《旧日飞车》现已推出ps5与nintendo switch平台的试玩版,并同步释出全新主机版本宣传影片。本作预计将于2025年结束抢先体验阶段,正式推出完整版本,并将登陆pc与多个主机平台。正式版内容将全面升级,包括超过30辆…

    2026年8月26日 用户投稿
    100
  • 抖店关店公示怎么写?抖音小店关店公示的添加模块怎么填写

    随着互联网的飞速发展,抖音小店成为了越来越多人的创业选择。由于各种原因,有些抖店可能会面临关店的风险。当你的抖店需要关店时,应该如何撰写一份合格的关店公示呢?本文将为你详细解答。 一、抖店关店公示的重要性 抖店关店公示是商家对消费者负责的表现,也是遵守相关法律法规的体现。一份详细的关店公示,可以: …

    2026年8月26日
    100
  • PHPUnit在Yii中的单元测试配置

    在yii中使用phpunit进行单元测试很重要,因为它能确保代码的功能性,捕捉错误,提高可维护性和可重用性。具体步骤包括:1) 安装phpunit和yii测试框架扩展;2) 配置测试环境,包括设置测试数据库和配置文件;3) 编写测试用例,放在tests/unit目录下。通过这些步骤和最佳实践,可以系…

    2026年8月26日
    000
  • 抖音直播商品可添加哪些电商渠道?抖音开直播添加商品按钮在哪

    随着互联网的飞速发展,直播带货逐渐成为电商领域的新宠。抖音作为国内领先的短视频平台,其直播带货功能更是吸引了大量商家和消费者的关注。抖音直播商品可以添加哪些电商渠道呢?本文将为您揭秘带货新玩法,让您在抖音直播中轻松实现商品销售! 一、抖音直播商品可添加的电商渠道 1. 抖音小店 抖音小店是抖音平台官…

    2026年8月26日
    100
  • 使用ThinkPHP开发微信小程序后端

    thinkphp适合开发微信小程序后端,因为它高效、简洁,功能丰富,性能良好,学习曲线平缓,社区活跃。1. 快速开发:设计理念支持快速迭代。2. 强大的orm:简化数据库操作。3. 灵活的路由系统:便于api设计。4. 丰富的中间件:支持认证和日志记录等功能。 在开发微信小程序后端时,选择Think…

    2026年8月26日
    000
  • 华硕主机内存双通道开启及性能对比测试

    华硕主机内存双通道开启及性能对比测试华硕主机内存双通道开启及性能对比测试华硕主机内存双通道开启及性能对比测试华硕主机内存双通道开启及性能对比测试

    华硕主机开启内存双通道确实能提升性能,尤其在内存带宽敏感的应用和集成显卡场景下。1. 首先确认主板支持双通道,通常有2条或4条插槽;2. 安装内存时选择同颜色或间隔空槽的插槽组合,如a2与b2或a1与b1;3. 查阅主板说明书确保安装正确;4. 开机进入bios/uefi,在“ai tweaker”…

    2026年8月26日 用户投稿
    000
  • Swoole的Reactor与Worker进程协作机制

    需要reactor与worker进程协作是因为这种机制能高效处理并发请求。1) reactor进程负责网络i/o操作,2) worker进程专注于业务逻辑处理,3) 这种分离提升了服务器的响应速度和吞吐量。 在探索Swoole的Reactor与Worker进程协作机制之前,我们先回答一个关键问题:为…

    2026年8月26日
    100
  • 快手私信怎么不在屏幕上显示?快手怎么隐藏别人私信

    随着科技的不断发展,短视频平台越来越受欢迎,快手作为其中的一员,深受广大用户的喜爱。在快手上,私信功能是大家沟通交流的重要途径,但有时候,我们可能不希望自己的私信在屏幕上显示,那么如何设置呢?接下来,我就来给大家详细讲解一下快手私信不在屏幕上显示的设置方法。 一、快手私信不在屏幕上显示的原因 在探讨…

    2026年8月26日
    100
  • 如何在Laravel中实现软删除(Soft Delete)?

    在laravel中实现软删除需要在模型中使用softdeletes trait,并声明deleted_at字段。具体步骤包括:1)在模型中引入softdeletes trait和声明deleted_at字段;2)使用withtrashed方法查询包含软删除记录,使用onlytrashed方法查询仅软…

    2026年8月26日
    400
  • RT-thread finsh移植到linux平台

    大家好,又见面了,我是你们的朋友全栈君。 目录 FinSH介绍 传统命令行模式 C 语言解释器模式 FinSH移植 移植要点 效果验证 代码下载 参考 在一次项目中, 需要进行嵌入式操作系统选型, 需求就是选择一款OS,既能满足当下项目的需要,又要考虑公司未来对物联网应用的扩展能力,对比了目前市面上…

    2026年8月26日
    100
  • 《歧路旅人0》新情报公开 游戏角色以及设施等

    《歧路旅人0》新情报公开 游戏角色以及设施等《歧路旅人0》新情报公开 游戏角色以及设施等《歧路旅人0》新情报公开 游戏角色以及设施等《歧路旅人0》新情报公开 游戏角色以及设施等

    se公布了hd-2d风格jrpg《歧路旅人》系列新作《歧路旅人0》,游戏将于12月4日登陆switch2 / ps5 / xbox series x|s / switch / ps4平台,pc版本则于12月5日上线。官方近日释出大量新情报,让我们一起来看看。 ·霸王帕蒂斯三世: 个性残暴的独裁君主,…

    2026年8月26日 用户投稿
    000
  • 替代传统WebSocket解决方案(如Socket.io)

    websocket替代方案包括sse、webrtc、长轮询和mqtt。1. sse适合服务器推送数据,简单但单向。2. webrtc适用于低延迟p2p通信,但复杂。3. 长轮询适用于实时性要求不高的应用,兼容性好。4. mqtt适合物联网和移动应用,需额外支持。选择方案需根据项目需求决定。 在现代W…

    2026年8月26日
    200
  • 无货如何在抖音上卖货卖别人家的货卖?自己没货怎么在抖音带货

    在如今的短视频热潮中,抖音已成为众多创业者和商家实现变现的重要阵地。不少人虽有意通过直播或短视频带货,却因自身没有货源、无法囤货而止步不前。其实,没有产品也能在抖音成功卖货!关键在于掌握“无货源模式”,巧妙借助他人商品资源进行推广销售。接下来,就为大家详细解析如何在零库存的情况下,在抖音上轻松卖别人…

    2026年8月26日
    100
  • 备份策略(数据库/代码/文件)设计

    设计有效备份策略至关重要,因为它是数据保护和业务连续性的基石。1)数据库备份:使用mysql脚本自动备份并管理生命周期。2)代码备份:使用git并定期备份到外部存储。3)文件备份:使用云存储并分类管理。 设计一个有效的备份策略对于任何组织或个人来说都是至关重要的,特别是在处理数据库、代码和文件时。这…

    2026年8月26日
    200

发表回复

登录后才能评论
关注微信