C++设计模式:状态模式(自动售货机)

什么是状态模式?

状态模式是一种行为型设计模式,它允许一个对象在其内部状态发生改变时,动态改变其行为。通过将状态相关的逻辑封装到独立的类中,状态模式能够将状态管理与行为解耦,从而让系统更加灵活和可维护。

通俗理解

想象一个自动售货机,它有以下几种状态:

  1. 无硬币状态:等待用户投入硬币。
  2. 有硬币状态:用户可以选择商品。
  3. 售货状态:用户选择商品后,售货机正在出货。
  4. 缺货状态:商品已售罄。

售货机的行为完全依赖于当前的状态,比如:

  • 无硬币状态下,用户无法选择商品。
  • 有硬币状态下,用户可以选择商品。
  • 售货状态下,售货机执行出货操作。

状态模式的核心就是:将状态逻辑抽象为独立的状态类,并通过上下文类(如售货机)动态切换状态对象,进而改变对象的行为。


状态模式的特点

优点
  1. 清晰封装状态逻辑

    • 每种状态的逻辑被集中到对应的状态类中,逻辑清晰且易于管理。
  2. 动态切换行为

    • 对象的行为可以通过切换状态动态改变,无需修改上下文类的代码。
  3. 扩展性强

    • 新增状态只需添加新的状态类,不影响现有代码,符合开闭原则
缺点
  1. 类的数量增加

    • 每种状态都需要一个单独的类,可能导致类数量较多。
  2. 状态切换逻辑分散

    • 状态之间的切换逻辑分布在多个状态类中,增加了维护的复杂性。

状态模式的结构

UML类图
         +---------------------+|       Context       |   // 上下文类,管理当前状态+---------------------+|  - state: State     ||  + setState(state)  ||  + request()        |+---------------------+^|+---------------------+|       State         |   // 抽象状态类+---------------------+|  + handle()         |+---------------------+^|+----------------------+  +----------------------+|  ConcreteStateA      |  |  ConcreteStateB      |   // 具体状态类+----------------------+  +----------------------+|  + handle()          |  |  + handle()          |+----------------------+  +----------------------+
组成部分
  1. State(抽象状态类)

    • 定义了所有状态的通用接口,例如insertCoin()selectProduct()等。
  2. ConcreteState(具体状态类)

    • 实现State接口,定义与特定状态相关的行为。
  3. Context(上下文类)

    • 持有一个状态对象,调用当前状态的行为。
    • 负责在运行时切换状态对象。

案例:自动售货机

需求描述

设计一个简单的自动售货机,支持以下功能:

  1. 无硬币状态:用户不能选择商品。
  2. 有硬币状态:用户可以选择商品。
  3. 售货状态:用户选择商品后,售货机出货。
目标
  • 实现售货机的状态管理,使得行为随着状态变化而动态改变。
  • 状态切换应简单且易于扩展。

完整代码实现

以下是状态模式在自动售货机中的应用,输出为中文,附带详细注释。

#include <iostream>
#include <memory>
#include <string>// 抽象状态类
class State {
public:virtual void insertCoin() = 0;      // 投入硬币virtual void selectProduct() = 0;  // 选择商品virtual void dispenseProduct() = 0; // 出货virtual ~State() = default;
};// 前向声明:解决状态类互相引用的问题
class VendingMachine;
class HasCoinState;// 上下文类:自动售货机
class VendingMachine {
private:std::shared_ptr<State> currentState; // 当前状态public:void setState(std::shared_ptr<State> state) {currentState = state; // 切换状态}// 调用当前状态的方法void insertCoin() {currentState->insertCoin();}void selectProduct() {currentState->selectProduct();}void dispenseProduct() {currentState->dispenseProduct();}
};// 具体状态类:无硬币状态
class NoCoinState : public State {
private:VendingMachine* machine; // 上下文public:explicit NoCoinState(VendingMachine* machine) : machine(machine) {}void insertCoin() override {std::cout << "硬币已投入,进入有硬币状态。" << std::endl;machine->setState(std::static_pointer_cast<State>(std::make_shared<HasCoinState>(machine))); // 切换到有硬币状态}void selectProduct() override {std::cout << "请先投入硬币。" << std::endl;}void dispenseProduct() override {std::cout << "请先投入硬币并选择商品。" << std::endl;}
};// 具体状态类:有硬币状态
class HasCoinState : public State {
private:VendingMachine* machine;public:explicit HasCoinState(VendingMachine* machine) : machine(machine) {}void insertCoin() override {std::cout << "硬币已存在,请选择商品。" << std::endl;}void selectProduct() override {std::cout << "商品已选择,正在出货。" << std::endl;machine->setState(std::static_pointer_cast<State>(std::make_shared<NoCoinState>(machine))); // 模拟出货后切换到无硬币状态}void dispenseProduct() override {std::cout << "请先选择商品。" << std::endl;}
};// 客户端代码
int main() {// 创建自动售货机并设置初始状态VendingMachine machine;machine.setState(std::make_shared<NoCoinState>(&machine));// 测试售货机的功能std::cout << "=== 测试场景 1:无硬币状态 ===" << std::endl;machine.selectProduct(); // 未投硬币时选择商品machine.insertCoin();    // 投入硬币std::cout << "\n=== 测试场景 2:有硬币状态 ===" << std::endl;machine.selectProduct(); // 投币后选择商品return 0;
}

运行结果

=== 测试场景 1:无硬币状态 ===
请先投入硬币。
硬币已投入,进入有硬币状态。=== 测试场景 2:有硬币状态 ===
商品已选择,正在出货。

代码解读

1. 抽象状态类(State
class State {
public:virtual void insertCoin() = 0;virtual void selectProduct() = 0;virtual void dispenseProduct() = 0;virtual ~State() = default;
};
  • 定义了所有状态的接口。
  • 子类实现这些方法来处理具体的状态逻辑。
2. 上下文类(VendingMachine
class VendingMachine {
private:std::shared_ptr<State> currentState;public:void setState(std::shared_ptr<State> state) {currentState = state;}void insertCoin() {currentState->insertCoin();}void selectProduct() {currentState->selectProduct();}void dispenseProduct() {currentState->dispenseProduct();}
};
  • 持有当前状态对象,并将行为委托给当前状态。
  • 通过setState方法切换状态。
3. 具体状态类
  • 无硬币状态
void insertCoin() override {std::cout << "硬币已投入,进入有硬币状态。" << std::endl;machine->setState(std::static_pointer_cast<State>(std::make_shared<HasCoinState>(machine)));
}
  • 有硬币状态
void selectProduct() override {std::cout << "商品已选择,正在出货。" << std::endl;machine->setState(std::static_pointer_cast<State>(std::make_shared<NoCoinState>(machine)));
}

每个具体状态实现了当前状态的行为逻辑,同时可以切换到其他状态。


状态模式的应用场景

  1. 对象行为取决于状态

    • 自动售货机、订单状态管理、游戏角色状态(如站立、跑动、跳跃等)。
  2. 需要消除复杂条件判断

    • 替代if-elseswitch语句,将状态逻辑分散到独立的状态类中。

总结

状态模式是一种非常实用的设计模式,它通过将状态逻辑封装到独立的类中,动态改变对象的行为,从而提高代码的扩展性和可维护性。

核心优势
  1. 行为动态改变:通过切换状态对象,动态改变对象的行为。
  2. 易扩展:新增状态只需添加状态类,不影响现有代码。
  3. 逻辑清晰:每个状态的逻辑集中在对应的状态类中,代码更易于理解和维护。
典型应用
  • 自动售货机
  • 在线订单状态(待支付、已支付、已发货等)
  • 游戏角色状态(站立、奔跑、跳跃等)

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

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

相关文章

【Pytorch实用教程】循环神经网络中使用dropout需要注意的问题

文章目录 问题解答警告的具体含义解决方案示例代码总结问题 UserWarning: dropout option adds dropout after all but last recurrent layer, so non-zero dropout expects num_layers greater than 1, but got dropout=0.3 and num_layers=1 warnings.warn("dropout op…

数据中台与数据治理服务方案[50页PPT]

本文概述了数据中台与数据治理服务方案的核心要点。数据中台作为政务服务数据化的核心&#xff0c;通过整合各部门业务系统数据&#xff0c;进行建模与加工&#xff0c;以新数据驱动政府管理效率提升与政务服务能力增强。数据治理则聚焦于解决整体架构问题&#xff0c;确保数据…

postgresq-自定义执行计划(custom plan)与generic plan(通用执行计划)

文章目录 之前写过一篇关于 PostgreSQL prepare sql的文章&#xff0c;但当时没有提到generic plan(通用计划)和custom plan(自定义计划)这两个概念。现在将通过举例介绍这两个概念。 创建测试表&#xff1a; postgres# create database demo; CREATE DATABASE postgres# \c d…

dockfile 配置 /etc/apt/source.list.d/debian.list 清华镜像

docker:3.12.7 镜像使用的是 debian 系统&#xff0c;比 ubuntu 更轻量。debian 系统内&#xff0c;apt 镜像源列表位于 /etc/apt/source.list.d/debian.list&#xff08;作为对比&#xff0c;ubuntu 的镜像列表位于 /etc/apt/source.list&#xff0c;二者语法相同&#xff09;…

程序员测试日常小工具

作为一名程序员&#xff0c;或者测试人员&#xff0c;日常工作最常用的工具有哪些&#xff0c;截图&#xff0c;截图漂浮&#xff0c;翻译&#xff0c;日期处理&#xff0c;api调用...&#xff0c; 当你拿到一串报文后&#xff0c;想要json转换时&#xff0c;是不是要打…

【MySQL高级】第1-4章

第1章 存储过程 1.1 什么是存储过程&#xff1f; 存储过程可称为过程化SQL语言&#xff0c;是在普通SQL语句的基础上增加了编程语言的特点&#xff0c;把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中&#xff0c;通过逻辑判断、循环等操作实现复杂计算的程序语言。 换…

深入浅出:AWT事件监听器及其应用

前言 在Java的GUI编程中&#xff0c;事件处理是非常重要的一环。AWT&#xff08;Abstract Window Toolkit&#xff09;框架提供了灵活的事件处理机制&#xff0c;使得开发者能够响应用户的操作&#xff0c;例如点击按钮、键盘输入、鼠标点击等。AWT的事件监听器就是实现这一机…

【Rust自学】8.5. HashMap Pt.1:HashMap的定义、创建、合并与访问

8.5.0. 本章内容 第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构&#xff0c;这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。 第八章中的集合是存储在堆内存上而非栈内存上的&#xff0c;这也意味着这些集合的数据大小无需在编…

混合合并两个pdf文件

混合两个pdf 1、在线免费交替和混合奇数和偶数PDF页面2、有什么软件把两个 PDF 交叉合并&#xff1f;3、pdfsam本地合并 如何Google翻译的原文和译文合并&#xff0c;&#xff08;沉浸式翻译效果相对较好&#xff09; 1、在线免费交替和混合奇数和偶数PDF页面 https://deftpd…

Hutool 发送 HTTP 请求的几种常见写法

最简单的 GET 请求&#xff1a; String result HttpUtil.get("https://www.baidu.com");带参数的 GET 请求&#xff1a; // 方法1: 直接拼接URL参数 String result HttpUtil.get("https://www.baidu.com?name张三&age18");// 方法2: 使用 HashMap…

获取用户详细信息-ThreadLocal优化

Thread全局接口可用&#xff0c;不用再重复编写。所以为了代码的复用&#xff0c;使用Thread。把之前的内容&#xff08;函数的参数和map与username&#xff09;注释掉&#xff0c;换为Thread传过来的内容&#xff08;map与username&#xff09;。 因为Thread需要在拦截器里面…

THUCNews解压/THUCNews数据集解压出问题

省流&#xff1a;使用zip64进行解压&#xff0c;文件数目太多windows默认zip16装不下 我在使用THUCNews中文文本数据集时出现了问题&#xff0c;原数据集解压后应该包含以下两个文件夹: 其中THUCNews文件夹下有以新闻类别命名的子文件。官网下载的是一个1.56GB的zip压缩包 而我…

MySQL使用通用二进制文件安装到Unix/Linux

Oracle提供了一组MySQL的二进制发行版。其中包括用于许多平台的压缩tar文件&#xff08;扩展名为.tar.xz的文件&#xff09;形式的通用二进制发行版&#xff0c;以及用于选定平台的特定平台包格式的二进制文件。 本节介绍在Unix/Linux平台上从压缩的tar文件二进制分布安装MySQ…

安卓/system/bin下命令中文说明(AI)

ATFWD-daemon&#xff1a;AT指令转发守护进程&#xff0c;用于将AT指令从应用层转发到调制解调器。 PktRspTest&#xff1a;数据包响应测试工具。 StoreKeybox&#xff1a;存储密钥盒工具&#xff0c;用于安全地存储加密密钥。 WifiLogger_app&#xff1a;WiFi日志记录应用&…

Git操作总结

可以直接看实践 总结自施磊老师课程 Git与SVN对比 svn操作流程 写代码。 从服务器拉回服务器的当前版本库&#xff0c;并解决服务器版本库与本地代码的冲突。 将本地代码提交到服务器。 Git操作流程 写代码&#xff0c; 然后添加&#xff08;add&#xff09;到暂存区。 …

直流开关电源技术及应用二

文章目录 8 PFC8.1 基本概念8.1.1 功率因数8.1.2 功率因数偏低带来的影响8.1.3 特点 8.2 有源功率因数校正原理8.2.1不连续工作模式的矫正原理恒频控制技术控制目标控制关键要素控制过程实现方式公式Boost电路和boost pfc电路的联系和区别联系区别 恒导通时间控制 8.2.2 连续工…

UNI-APP_i18n国际化引入

官方文档&#xff1a;https://uniapp.dcloud.net.cn/tutorial/i18n.html vue2中使用 1. 新建文件 locale/index.js import en from ./en.json import zhHans from ./zh-Hans.json import zhHant from ./zh-Hant.json const messages {en,zh-Hans: zhHans,zh-Hant: zhHant }…

typora+picgo core+minio自动上传图片

1. 在服务器上安装docker版本minio 创建/docker/minio文件夹 mkdir -p /docker/minio在此文件夹创建docker-compose.yml version: "3.5" services:minio:image: quay.io/minio/minio:latestcontainer_name: minioprivileged: truerestart: alwaysports:# API接口访…

论文笔记:DepthLab: From Partial to Complete

是一篇很精炼的论文&#xff0c;不知道咋总结了&#xff0c;就差全文翻译了&#xff0c;不过在这里我主要关注3D部分&#xff0c;因为他的pipeline是基于SD的&#xff0c;框图也比较清晰易懂&#xff0c;非常细节的内容可以回头看论文&#xff0c;哈哈哈&#xff0c;给作者大佬…

LeetCode--排序算法(堆排序、归并排序、快速排序)

排序算法 归并排序算法思路代码时间复杂度 堆排序什么是堆&#xff1f;如何维护堆&#xff1f;如何建堆&#xff1f;堆排序时间复杂度 快速排序算法思想代码时间复杂度 归并排序 算法思路 归并排序算法有两个基本的操作&#xff0c;一个是分&#xff0c;也就是把原数组划分成…