设计模式之状态模式:自动售货机的喜怒哀乐

news2025/1/1 14:35:19

在这里插入图片描述

~犬📰余~

“我欲贱而贵,愚而智,贫而富,可乎?
曰:其唯学乎”

一、状态模式概述

\quad 在我们的日常生活中,很多事物都具有不同的状态。比如我们经常使用的自动售货机,它就具有多种状态:空闲状态(等待投币)、已投币状态(等待选择商品)、出货状态(正在出货)等。在每种状态下,售货机对用户的操作会产生不同的响应。例如,在空闲状态下投币,机器会切换到已投币状态;而在已投币状态下投币,机器则会直接退币。
\quad 这种根据不同状态对相同操作做出不同响应的场景在软件开发中非常常见。最直观的解决方案是使用大量的 if-else 或 switch-case 语句来判断当前状态并执行相应的操作。但这样的代码往往会变得臃肿、难以维护,而且违反了"开闭原则"——每次添加新状态都需要修改判断语句。
\quad 状态模式就是为了解决这类问题而设计的。它的核心思想是将不同状态的行为抽象成独立的类,使得状态的切换只需要改变对象的状态属性即可。这样一来,每个状态的行为都被封装在对应的状态类中,各个状态之间互不干扰,系统便具有了良好的可扩展性和维护性。
\quad 让我们看一下状态模式的基本结构:
图片

二、状态模式的角色组成

\quad 状态模式主要由三种角色组成,它们各司其职又相互配合,共同实现了状态转换的灵活管理。就像一台自动售货机需要有机器本身(管理者)、不同的运行状态(状态抽象)以及每种具体状态下的操作逻辑(具体状态实现)一样,状态模式中的每个角色也都有其特定的职责。

  • Context(环境类):相当于状态模式中的"管理者",它维护一个对当前状态对象的引用,并负责状态的切换。在我们的自动售货机例子中,Context 就是售货机本身,它需要知道当前处于什么状态,并根据用户的操作来改变状态。
  • State(抽象状态类):定义了一个接口或抽象类,用于封装与 Context 的一个特定状态相关的行为。就像我们在定义售货机状态时,需要规定所有状态都应该能够处理投币、退币、选择商品等基本操作一样。
  • ConcreteState(具体状态类):实现了抽象状态类定义的接口,为 Context 的每一个具体状态提供实际的行为实现。例如,在售货机的"空闲状态"下,投币操作会将状态切换为"已投币状态";而在"已投币状态"下,投币操作则会直接退回硬币。

\quad 当用户触发某个操作时,Context 会将请求委托给当前的 State 对象来处理。每个 ConcreteState 都知道在当前状态下应该如何处理这个请求,以及在什么情况下需要切换到其他状态。通过这种方式,系统的状态转换逻辑被分散到各个状态类中,避免了在一个类中堆积大量的条件判断语句。

三、状态模式案例

\quad 为了更好地理解状态模式的实际应用,让我们以自动售货机为例来实现一个完整的状态模式示例。首先看一下自动售货机的状态转换图:
在这里插入图片描述

\quad 现在让我们通过代码来实现这个自动售货机系统:

// 抽象状态类
public abstract class VendingMachineState {
    protected VendingMachine machine;

    public VendingMachineState(VendingMachine machine) {
        this.machine = machine;
    }

    // 投币操作
    public abstract void insertCoin();

    // 退币操作
    public abstract void ejectCoin();

    // 选择商品
    public abstract void selectProduct();

    // 发放商品
    public abstract void dispense();
}

// 空闲状态(等待投币)
public class IdleState extends VendingMachineState {
    public IdleState(VendingMachine machine) {
        super(machine);
    }

    @Override
    public void insertCoin() {
        System.out.println("投币成功");
        machine.setState(new HasCoinState(machine));
    }

    @Override
    public void ejectCoin() {
        System.out.println("没有硬币,无法退币");
    }

    @Override
    public void selectProduct() {
        System.out.println("请先投币");
    }

    @Override
    public void dispense() {
        System.out.println("请先投币");
    }
}

// 已投币状态
public class HasCoinState extends VendingMachineState {
    public HasCoinState(VendingMachine machine) {
        super(machine);
    }

    @Override
    public void insertCoin() {
        System.out.println("已经有硬币,无需再投");
    }

    @Override
    public void ejectCoin() {
        System.out.println("退币成功");
        machine.setState(new IdleState(machine));
    }

    @Override
    public void selectProduct() {
        System.out.println("商品选择成功,正在出货...");
        machine.setState(new DispensingState(machine));
    }

    @Override
    public void dispense() {
        System.out.println("请先选择商品");
    }
}

// 出货状态
public class DispensingState extends VendingMachineState {
    public DispensingState(VendingMachine machine) {
        super(machine);
    }

    @Override
    public void insertCoin() {
        System.out.println("正在出货,请稍等");
    }

    @Override
    public void ejectCoin() {
        System.out.println("正在出货,无法退币");
    }

    @Override
    public void selectProduct() {
        System.out.println("正在出货,请稍等");
    }

    @Override
    public void dispense() {
        System.out.println("商品已发放");
        machine.setState(new IdleState(machine));
    }
}

// 自动售货机类(Context)
public class VendingMachine {
    private VendingMachineState state;

    public VendingMachine() {
        state = new IdleState(this);
    }

    public void setState(VendingMachineState state) {
        this.state = state;
    }

    public void insertCoin() {
        state.insertCoin();
    }

    public void ejectCoin() {
        state.ejectCoin();
    }

    public void selectProduct() {
        state.selectProduct();
        state.dispense();
    }
}

// 测试类
public class StatePatternDemo {
    public static void main(String[] args) {
        VendingMachine machine = new VendingMachine();

        // 测试初始状态(空闲状态)
        System.out.println("===== 测试空闲状态 =====");
        machine.selectProduct();  // 请先投币
        machine.ejectCoin();      // 没有硬币,无法退币

        // 测试投币
        System.out.println("\n===== 测试投币 =====");
        machine.insertCoin();     // 投币成功
        machine.insertCoin();     // 已经有硬币,无需再投

        // 测试选择商品
        System.out.println("\n===== 测试选择商品 =====");
        machine.selectProduct();  // 商品选择成功,正在出货... -> 商品已发放

        // 测试回到初始状态
        System.out.println("\n===== 测试回到初始状态 =====");
        machine.selectProduct();  // 请先投币
    }
}

\quad 测试结果:
图片
\quad 在这个自动售货机的实现中,我们可以看到状态模式的几个关键特点:

  • 状态的封装:每个状态都被封装在独立的类中,包含了该状态下所有可能的行为实现。例如,IdleState 类处理了空闲状态下的所有操作响应。
  • 状态转换的解耦:状态之间的转换被封装在各个状态类内部。比如,当在空闲状态下投币时,IdleState 类负责创建新的 HasCoinState 对象并更新机器状态。
  • Context 类的简化:VendingMachine 类不需要关心具体的状态处理逻辑,它只需要将请求委托给当前状态对象即可。这使得 Context 类变得非常简洁。
  • 行为的一致性:通过抽象状态类 VendingMachineState 的定义,确保了所有具体状态类都实现了统一的接口,便于系统的扩展和维护。

\quad 运行这段代码,我们可以看到自动售货机在不同状态下对相同操作会产生不同的响应,这正是状态模式的精髓所在。

四、状态模式的优缺点

4.1. 优点

\quad 状态模式最大的优势在于它将状态相关的行为分散到独立的类中。比如在我们的自动售货机示例中,每个状态(空闲、已投币、出货)的处理逻辑都被清晰地封装在对应的类中。这种设计带来了良好的代码组织结构:当我们需要修改某个状态的行为时,只需要修改对应的状态类,不会影响到其他状态的代码。
\quad 其次,状态模式消除了传统实现中大量的条件语句。如果不使用状态模式,我们可能需要在售货机类中编写大量的 if-else 来判断当前状态并执行相应的操作。而使用状态模式后,这些判断都被转化为了多态调用,代码更加优雅和易于维护。
\quad 状态模式还使得状态转换变得更加明确和可控。每个状态类都清楚地知道在什么情况下需要切换到其他状态,这种显式的状态管理方式大大降低了出错的可能性。

4.2. 缺点

\quad 最明显的缺点是可能会导致类的数量增多。在我们的示例中仅有三个状态,就需要创建三个状态类。如果系统的状态数量很多,就会产生大量的状态类,这会增加系统的复杂度。
\quad 另外,状态模式将状态转换的逻辑分散在各个状态类中,虽然这提高了代码的清晰度,但也使得状态之间的转换关系变得不那么直观。我们需要查看多个状态类才能理清楚完整的状态转换图。

五、状态模式的适用场景

\quad 状态模式在实际开发中有着广泛的应用场景。除了我们讨论的自动售货机示例,它还特别适用于以下场景:

  • 游戏开发中的角色状态管理就是一个典型的应用场景。比如游戏角色可能有站立、行走、跳跃、攻击等多个状态,在每个状态下对用户输入的响应都不同。使用状态模式可以将这些复杂的状态行为清晰地组织起来。
  • 订单系统也经常使用状态模式来管理订单状态。一个订单可能经历待支付、已支付、已发货、已签收等多个状态,每个状态下允许的操作都不同。状态模式可以帮助我们构建一个清晰的订单状态管理框架。
  • 文档处理系统中的文档状态(草稿、审核中、已发布等)、TCP 连接状态管理(连接中、已连接、断开连接等)都是状态模式的适用场景。总的来说,当一个对象需要在多个状态之间切换,并且在不同状态下对外呈现不同的行为时,状态模式都是一个很好的选择。

\quad 在选择是否使用状态模式时,我们需要权衡系统的状态数量和状态转换的复杂度。如果系统只有两三个状态,且状态转换逻辑相对简单,使用普通的条件语句可能更加直观。但如果系统包含多个状态,且状态之间的转换规则复杂,那么状态模式就能够很好地发挥其优势。

六、总结

\quad 状态模式为我们提供了一种优雅的方式来管理对象的状态变化。通过将不同状态的行为封装到独立的类中,它实现了更好的代码组织结构和更清晰的状态转换逻辑。就像我们在自动售货机示例中看到的,状态模式不仅使代码更容易理解和维护,还提供了良好的扩展性。
\quad 在实际应用中,状态模式的价值不仅体现在消除了复杂的条件判断,更重要的是它遵循了"开闭原则",使得我们可以在不修改现有代码的情况下添加新的状态。这一点在大型系统的开发和维护中尤为重要。
\quad 值得注意的是,状态模式并不是管理状态的唯一方案。在选择使用状态模式时,我们需要考虑系统的具体需求,权衡代码的复杂度和灵活性。有时候,简单的条件语句可能是更好的选择。设计模式的使用不是目的,解决问题才是关键。
在这里插入图片描述

关注犬余,共同进步

技术从此不孤单

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

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

相关文章

信息系统管理工程第8章思维导图

软考信管第8章的思维导图也实在是太长了,制作的耗时远超过之前的预计。给你看看思维导图的全貌如下,看看你能够在手机上滚动多少个屏幕 当你看到这段文字的时候,证明你把思维导图从上到下看完了,的确很长吧,第8章的教程…

Excel无法插入新单元格怎么办?有解决方法吗?

在使用Excel时,有时会遇到无法插入新单元格的困扰。这可能是由于多种原因导致的,比如单元格被保护、冻结窗格、合并单元格等。本文将详细介绍3种可能的解决方案,帮助你顺利插入新单元格。 一、消冻结窗格 冻结窗格功能有助于在滚动工作表时保…

深度学习笔记(12)——深度学习概论

深度学习概论 深度学习关系: 为什么机器人有一部分不在人工智能里面:机器人技术是一个跨学科的领域,它结合了机械工程、电子工程、计算机科学以及人工智能(AI)等多个领域的知识。 并不是所有的机器人都依赖于人工智能…

HEIC 是什么图片格式?如何把 iPhone 中的 HEIC 转为 JPG?

在 iPhone 拍摄照片时,默认的图片格式为 HEIC。虽然 HEIC 格式具有高压缩比、高画质等优点,但在某些设备或软件上可能存在兼容性问题。因此,将 HEIC 格式转换为更为通用的 JPG 格式就显得很有必要。本教程将介绍如何使用简鹿格式工厂&#xf…

flask后端开发(11):User模型创建+注册页面模板渲染

目录 一、数据库创建和配置信息1.新建数据库2.数据库配置信息3.User表4.ORM迁移 二、注册页面模板渲染1.导入静态文件2.蓝图注册路由 一、数据库创建和配置信息 1.新建数据库 终端中 CREATE DATABASE zhiliaooa DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;2…

【Next.js】001-项目初始化

【Next.js】001-项目初始化 文章目录 【Next.js】001-项目初始化一、前言二、自动创建项目1、环境要求2、创建项目创建命令创建演示生成的项目目录如果你不使用 npx 命令 3、运行项目脚本说明在开发环境运行项目查看页面 4、示例代码说明创建项目查看示例项目创建项目命令创建过…

系统安全——可信计算

可信计算 可信计算的起源 上世纪八十年代,TCSEC标准将系统中所有安全机制的总和定义为可信计算基 (Trusted Computing Base TCB) TCB的要求是: 独立的(independent) 具有抗篡改性 tempering proof 不可旁路(无法窃…

Python学生管理系统(MySQL)

上篇文章介绍的Python学生管理系统GUI有不少同学觉得不错来找博主要源码,也有同学提到老师要增加数据库管理数据的功能,本篇文章就来介绍下python操作数据库,同时也对上次分享的学生管理系统进行了改进了,增加了数据库&#xff0c…

【Sentinel】流控效果与热点参数限流

目录 1.流控效果 1.1.warm up 2.2.排队等待 1.3.总结 2.热点参数限流 2.1.全局参数限流 2.2.热点参数限流 2.3.案例 1.流控效果 在流控的高级选项中,还有一个流控效果选项: 流控效果是指请求达到流控阈值时应该采取的措施,包括三种&…

《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》学习笔记——HarmonyOS技术理念

1.2 技术理念 在万物智联时代重要机遇期,HarmonyOS结合移动生态发展的趋势,提出了三大技术理念(如下图3-1所示):一次开发,多端部署;可分可合,自由流转;统一生态&#xf…

计算机组成——Cache

目录 为什么引入高速缓存? 数据查找方案: 命中率与缺失率 Cache和主存的映射方式 1.全相联映射 经典考法 覆盖问题 访存 2.组相联映射 3.直接映射(和组相联类似) 覆盖问题 替换算法 1.随机算法(RAND&…

OpenCV和PyQt的应用

1.创建一个 PyQt 应用程序,该应用程序能够: 使用 OpenCV 加载一张图像。在 PyQt 的窗口中显示这张图像。提供四个按钮(QPushButton): 一个用于将图像转换为灰度图一个用于将图像恢复为原始彩色图一个用于将图像进行翻…

基于Spring Boot的宠物领养系统的设计与实现(代码+数据库+LW)

摘 要 如今社会上各行各业,都在用属于自己专用的软件来进行工作,互联网发展到这个时候,人们已经发现离不开了互联网。互联网的发展,离不开一些新的技术,而新技术的产生往往是为了解决现有问题而产生的。针对于宠物领…

uniapp 判断多选、选中取消选中的逻辑处理

一、效果展示 二、代码 1.父组件: :id=“this.id” : 给子组件传递参数【id】 @callParentMethod=“takeIndexFun” :给子组件传递方法,这样可以在子组件直接调用父组件的方法 <view @click="$refs.member.open()"

百度热力图数据日期如何选择

目录 1、看日历2、看天气 根据研究内容定&#xff0c;一般如果研究城市活力的话&#xff0c;通常会写“非重大节假日&#xff0c;非重大活动&#xff0c;非极端天气等”。南方晴天不多&#xff0c;有小雨或者中雨都可认为没有影响&#xff0c;要不然在南方很难找到完全一周没有…

【深入理解SpringCloud微服务】Sentinel源码解析——FlowSlot流控规则

Sentinel源码解析——FlowSlot流控规则 StatisticNode与StatisticSlotStatisticNode内部结构StatisticSlot FlowSlot流控规则 在前面的文章&#xff0c;我们对Sentinel的原理进行了分析&#xff0c;Sentinel底层使用了责任链模式&#xff0c;这个责任链就是ProcessorSlotChain对…

【机器学习(九)】分类和回归任务-多层感知机(Multilayer Perceptron,MLP)算法-Sentosa_DSML社区版 (1)11

文章目录 一、算法概念11二、算法原理&#xff08;一&#xff09;感知机&#xff08;二&#xff09;多层感知机1、隐藏层2、激活函数sigma函数tanh函数ReLU函数 3、反向传播算法 三、算法优缺点&#xff08;一&#xff09;优点&#xff08;二&#xff09;缺点 四、MLP分类任务实…

R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别

全文链接&#xff1a;https://tecdat.cn/?p38667 摘要&#xff1a;在母婴暴露于空气污染对儿童健康影响的研究中&#xff0c;常需对孕期暴露情况与健康结果进行回归分析。分布滞后非线性模型&#xff08;DLNM&#xff09;是一种常用于估计暴露 - 时间 - 响应函数的统计方法&am…

e3 1220lv3 cpu-z分数

e3 1220lv3 双核四线程&#xff0c;1.1G频率&#xff0c;最低可在800MHZ运行&#xff0c;TDP 13W。 使用PE启动后测试cpu-z分数。 现在e3 1220lv3的价格落到69元。

Debian安装配置RocketMQ

安装配置 本次安装在/tools/rocket目录下 下载 wget https://dist.apache.org/repos/dist/release/rocketmq/5.3.1/rocketmq-all-5.3.1-bin-release.zip 解压缩 unzip rocketmq-all-5.3.1-bin-release.zip 如果出现以下报错 -bash: unzip: command not found可安装unzip工具后执…