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

在这里插入图片描述

~犬📰余~

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

一、状态模式概述

\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 值得注意的是,状态模式并不是管理状态的唯一方案。在选择使用状态模式时,我们需要考虑系统的具体需求,权衡代码的复杂度和灵活性。有时候,简单的条件语句可能是更好的选择。设计模式的使用不是目的,解决问题才是关键。
在这里插入图片描述

关注犬余,共同进步

技术从此不孤单

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

4.银河麒麟V10(ARM) 离线安装 MySQL

1. 系统版本 [rootga-sit-cssjgj-db-01u ~]# nkvers ############## Kylin Linux Version ################# Release: Kylin Linux Advanced Server release V10 (Lance)Kernel: 4.19.90-52.39.v2207.ky10.aarch64Build: Kylin Linux Advanced Server release V10 (SP3) /(La…

多模态论文笔记——LLaVA

大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍多模态模型:LLaVA。处理包含图像和文本的多模态数据,并生成合理准确的回答。 文章目录 论文模型架构视觉编码器语言模型多模态融…

【Sentinel】初识Sentinel

目录 1.1.雪崩问题及解决方案 1.1.1.雪崩问题 1.1.2.超时处理 1.1.3.仓壁模式 1.1.4.断路器 1.1.5.限流 1.1.6.总结 1.2.服务保护技术对比 1.3.Sentinel介绍和安装 1.3.1.初识Sentinel 1.3.2.安装Sentinel 1.4.微服务整合Sentinel 1.1.雪崩问题及解决方案 1.1.1.…

[A-24][V-09]ARMv8/v9-SMMU工作场景与SMMU的虚拟化架构

ver0.1 [看前序文章有惊喜,关注W\X\G=Z+H=“浩瀚架构师”,可以解锁全部文章] 前言 我们在介绍ARM的内存体系的时候,行文中经常讲MMU比作PE-Cores的带刀护卫。按照这个逻辑,那么SMMU也可以称之为总线上各个Master(设备)的带刀护卫,利刃出鞘之后,任何驱动送过来的地址都…

WebRTC服务质量(10)- Pacer机制(02) RoundRobinPacketQueue

WebRTC服务质量(01)- Qos概述 WebRTC服务质量(02)- RTP协议 WebRTC服务质量(03)- RTCP协议 WebRTC服务质量(04)- 重传机制(01) RTX NACK概述 WebRTC服务质量(…

硬件设计-时钟振荡器

目录 摘要 壳式晶振 正常工作条件 摘要 本章主要介绍了晶振的分类、各项参数的意义、特点,同时也介绍了时钟抖动的成因、测量 方法、消除措施和典型滤波电路,使得我们可以正确地选择和使用晶振。 壳式晶振 如图 所示,壳式晶振的名字来源于…

Redis基础知识分享(含5种数据类型介绍+增删改查操作)

一、redis基本介绍 1.redis的启动 服务端启动 pythonubuntu:~$ redis-server客户端启动 pythonubuntu:~$ redis-cli <127.0.0.1:6379> exit pythonubuntu:~$ redis-cli --raw //(支持中文的启动方式) <127.0.0.1:6379> exit2.redis基本操作 ping发送给服务器…

sql字段值转字段

表alertlabel中记录变字段 如何用alertlabel表得到下面数据 实现的sql语句 select a.AlertID, (select Value from alertlabel where AlertIDa.AlertID and Labelhost) as host, (select Value from alertlabel where AlertIDa.AlertID and Labeljob) as job from (select …

llamafactory报错:双卡4090GPU,训练qwen2.5:7B、14B时报错GPU显存不足(out of memory),轻松搞定~~~

实际问题场景&#xff1a; 使用llamafactory进行微调qwen2.5 7B和14B的大模型时&#xff0c;会出现out of memory的报错。尝试使用降低batch_size&#xff08;原本是2&#xff0c;现在降到1&#xff09;的方式&#xff0c;可以让qwen2.5:7B跑起来&#xff0c;但时不时会不稳定…

【hackmyvm】hacked靶机wp

tags: HMVrootkitDiamorphine Type: wp 1. 基本信息^toc 文章目录 1. 基本信息^toc2. 信息收集2.1. 端口扫描2.2. 目录扫描2.3. 获取参数 3. 提权 靶机链接 https://hackmyvm.eu/machines/machine.php?vmHacked 作者 sml 难度 ⭐️⭐️⭐️⭐️️ 2. 信息收集 2.1. 端口扫描…

.NET平台用C#通过字节流动态操作Excel文件

在.NET开发中&#xff0c;通过字节流动态操作Excel文件提供了一种高效且灵活的方式处理数据。这种方法允许开发者直接在内存中创建、修改和保存Excel文档&#xff0c;无需依赖直接的文件储存、读取操作&#xff0c;从而提高了程序的性能和安全性。使用流技术处理Excel不仅简化了…

应用层1——C/S、P2P、DNS域名系统

目录 一、网络应用模型 1、C/S 2、p2p模型 二、域名解析系统DNS 1、为什么有DNS系统&#xff1f; 2、域名的特点 3、DNS域名系统原理 4、递归查询、迭代查询 5、常用的根域名与顶级域名 一、网络应用模型 1、C/S 客户/服务器模型 客户请求服务&#xff0c;服务器提供…

【疑难杂症】 HarmonyOS NEXT中Axios库的响应拦截器无法拦截424状态码怎么办?

今天在开发一个HarmonyOS NEXT的应用的时候&#xff0c;发现http接口如果返回的状态码是424时&#xff0c;我在axios中定义的拦截器失效了。直接走到了业务调用的catch中。 问题表现&#xff1a; 我的拦截器代码如下&#xff1a; 解决办法&#xff1a; 先说解决办法&#xff…

在Windows上读写Linux磁盘镜像的一种方法

背景 嵌入式开发中&#xff0c;经常会把系统的Linux磁盘镜像保存到Windows上&#xff0c;以便上传到网盘备份或发送给工厂&#xff0c;但是如果想读取/修改镜像中的某个文件&#xff0c;一般有2种方案&#xff1a; 直接访问 就是用虚拟磁盘软件将镜像文件挂载成磁盘&#xf…

ffmpeg之显示一个yuv照片

显示YUV图片的步骤 1.初始化SDL库 目的&#xff1a;确保SDL库正确初始化&#xff0c;以便可以使用其窗口、渲染和事件处理功能。操作&#xff1a;调用 SDL_Init(SDL_INIT_VIDEO) 来初始化SDL的视频子系统。 2.创建窗口用于显示YUV图像&#xff1a; 目的&#xff1a;创建一个…

Windows下播放文件作为麦克风声源的一种方式

近期测试一种外语的ASR识别成功率&#xff0c;样本素材是懂这门语言的同事录制的mp3文件。测试client端原本是从麦克风拾音生成媒体流的。 这样&#xff0c;就需要想办法把mp3文件转换为测试client的输入声音。物理方式上&#xff0c;可以用一根音频线&#xff0c;把电…

如何在网页端使用 IDE 高效地阅读 GitHub 源码?

如何在网页端使用 IDE 高效地阅读 GitHub 源码&#xff1f; 前言什么是 GitHub1s&#xff1f;使用 GitHub1s 阅读 browser-use 项目源码步骤 1: 打开 GitHub 项目页面步骤 2: 修改 URL 使用 GitHub1s步骤 3: 浏览文件结构步骤 4: 使用代码高亮和智能补全功能步骤 5: 快速跳转和…

Microsoft word@【标题样式】应用不生效(主要表现为在导航窗格不显示)

背景 随笔。Microsoft word 2013基础使用&#xff0c;仅做参考和积累。 问题 Microsoft word 2013&#xff0c;对段落标题文字应用【标题样式】不生效&#xff08;主要表现为在导航窗格不显示&#xff09;。 图1 图2 观察图1和图2&#xff0c;发现图1的文字在应用【标题一】样…

2021.12.28基于UDP同信的相关流程

作业 1、将TCP的CS模型再敲一遍 服务器 #include <myhead.h> #define PORT 8888 #define IP "192.168.124.123" int main(int argc, const char *argv[]) {//创建套接字//绑定本机IP和端口号//监听客户端请求//接收客户端连接请求//收发消息//创建套接字int…

OpenCV和PyQt的应用

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