news 2026/7/17 20:30:58

设计模式之状态模式:面试官让我写机器人行为切换逻辑,我写了一堆if-else被嫌弃

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式之状态模式:面试官让我写机器人行为切换逻辑,我写了一堆if-else被嫌弃

上篇聊了观察者模式和策略模式。今天来说另一个在机器人开发里特别实用的模式——状态模式。

先讲个面试场景。

面试官说:"假设你做一个服务机器人,它有巡逻、充电、避障、回充这几种行为,你怎么设计这个行为切换的逻辑?"

我当时怎么答的?一个Robot类,里面一堆枚举加if-else:

enum class RobotState { PATROL, CHARGING, AVOIDING, RETURNING }; class Robot { RobotState state_; void update() { if (state_ == RobotState::PATROL) { patrol(); if (batteryLow()) state_ = RobotState::RETURNING; if (obstacleDetected()) state_ = RobotState::AVOIDING; } else if (state_ == RobotState::CHARGING) { charge(); if (batteryFull()) state_ = RobotState::PATROL; } else if (state_ == RobotState::AVOIDING) { avoid(); if (obstacleCleared()) state_ = RobotState::PATROL; } else if (state_ == RobotState::RETURNING) { returnToStation(); if (atStation()) state_ = RobotState::CHARGING; } } };

面试官看完说:"如果再加十种状态呢?这个函数会变成什么样?"

确实会崩。这就是状态模式要解决的问题。

状态模式的核心思想

状态模式的思路其实很直觉:把每个状态封装成一个独立的类,状态之间的切换逻辑也放在各自的类里面。主类不再负责具体行为,只维护一个当前状态的引用。

class RobotState { public: virtual void handle(Robot& robot) = 0; virtual ~RobotState() = default; }; class Robot { private: unique_ptr<RobotState> state_; public: void setState(unique_ptr<RobotState> s) { state_ = std::move(s); } void update() { state_->handle(*this); } };

每个具体状态是一个类,继承RobotState:

class PatrolState : public RobotState { public: void handle(Robot& robot) override { robot.patrol(); if (robot.batteryLow()) { robot.setState(make_unique<ReturningState>()); } if (robot.obstacleDetected()) { robot.setState(make_unique<AvoidingState>()); } } }; class ChargingState : public RobotState { public: void handle(Robot& robot) override { robot.charge(); if (robot.batteryFull()) { robot.setState(make_unique<PatrolState>()); } } };

这样加新状态只需要加一个新类,不用改任何已有代码。开闭原则,还记得吧?

在机器人行为管理中的实际应用

ROS2里有个行为树(Behavior Tree)框架叫BehaviorTree.CPP,做机器人导航的基本都在用。行为树的每个节点,其实就是一种状态。

状态模式和行为树的区别,面试偶尔会聊到。行为树更灵活,可以组合和并行执行;状态模式更简洁,适合状态之间互斥切换的场景。

实际项目里,如果你的机器人行为是"同一时间只做一件事",状态模式就够了。比如一个扫地机器人:清扫、回充、待机、脱困,四个状态来回切。

class CleaningState : public RobotState { PathPlanner& planner_; public: CleaningState(PathPlanner& p) : planner_(p) {} void handle(Robot& robot) override { auto target = planner_.nextTarget(); robot.moveToward(target); if (robot.batteryLevel() < 15) { robot.setState(make_unique<ChargingState>()); } else if (robot.stuckDetected()) { robot.setState(make_unique<DeStuckState>()); } else if (robot.cleaningDone()) { robot.setState(make_unique<IdleState>()); } } };

还有一个好处:状态的进入和退出动作可以明确管理。进入巡逻状态时启动激光雷达,退出时关闭——这些逻辑写在状态类的构造和析构里,比在switch-case里到处插flag清晰太多了。

状态模式的几个变体

面试如果被追问"状态模式还有什么花样",可以提两个。

第一个是状态机表驱动法。不用类继承,而是用一张表描述"当前状态+事件→下一状态"的映射。适合状态多但逻辑简单的场景:

using TransitionTable = map<pair<State, Event>, State>; TransitionTable table = { {{PATROL, BATTERY_LOW}, RETURNING}, {{RETURNING, AT_STATION}, CHARGING}, {{CHARGING, BATTERY_FULL}, PATROL}, {{PATROL, OBSTACLE}, AVOIDING}, {{AVOIDING, CLEAR}, PATROL} };

第二个是层级状态机(Hierarchical State Machine)。状态可以嵌套,子状态继承父状态的行为。比如"导航中"这个大状态下面有"规划路径"和"跟踪路径"两个子状态。这个在无人机飞控里用得比较多。

面试中的关键考点

面试官问状态模式,几个经典追问:

"状态模式和策略模式有什么区别?"结构上看几乎一样——都是上下文加一个策略/状态的接口。核心区别在于:策略模式中,用户主动选择不同的策略;状态模式中,状态的切换是自动的,由状态内部决定。另外,策略之间通常不互相切换,而状态之间会频繁流转。

"状态模式怎么避免状态对象频繁创建?"如果状态切换很频繁,每次都new一个状态对象确实有开销。解决办法是状态对象做成单例或者预分配——如果状态不携带数据,所有实例都一样,完全可以用一个静态实例。

"状态模式和switch-case比有什么优势?"主要是可维护性。switch-case的问题在于,每加一个状态就要改那个巨大的switch,违反开闭原则。而且状态多了之后,switch里很容易漏掉某些case。状态模式把每个状态的行为局部化了,改一个状态不会影响其他状态。

给正在准备面试的你

状态模式在设计模式面试里属于"中等频率"考点。不像工厂模式和单例那样几乎必考,但在机器人、游戏这些涉及行为管理的领域,面试官很喜欢问。

准备的时候,建议手写一个三到四个状态的状态机,比如交通灯(红→绿→黄→红)、自动售货机(待机→投币→出货→找零)。能写出来,能讲清楚和策略模式的区别,面试就够用了。

下篇聊SOLID原则——面试官问"你怎么设计类的"时的评判标准。这个知识点是面向对象设计的根基和基础,前面聊的所有设计模式,背后都在遵循这些原则。


如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。

「机器人软件开发面试·从入门到精通」连载系列上一篇:第47篇 设计模式之观察者与策略——ROS2回调与行为切换 下一篇预告:第49篇 SOLID原则——面试官问"你怎么设计类的"评判标准

有任何问题欢迎评论区留言,我会尽量回复。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 20:28:48

WSL命令大全:从安装到高级配置实战指南

1. WSL基础命令全解析Windows Subsystem for Linux&#xff08;WSL&#xff09;已经成为开发者跨平台工作的标配工具。作为在Windows系统内原生运行Linux二进制文件的兼容层&#xff0c;它完美解决了开发环境统一的问题。我日常90%的Linux操作都通过WSL完成&#xff0c;下面这些…

作者头像 李华
网站建设 2026/7/17 20:28:31

Win10 22H2迅雷下载与官方镜像全解析

1. Win10 22H2版本迅雷下载资源全解析作为Windows 10的最后一个重大更新版本&#xff0c;22H2&#xff08;2022年10月更新&#xff09;至今仍是企业环境和老旧设备的主力选择。最近在技术社区发现不少用户在寻找该版本的官方镜像下载渠道&#xff0c;特别是希望通过迅雷这类P2P…

作者头像 李华
网站建设 2026/7/17 20:26:10

深入解析Linux文件系统接口原理与优化实践

1. 文件系统接口&#xff1a;操作系统的数据管家 刚接触操作系统的学习者常会困惑&#xff1a;为什么删除U盘文件前要"安全弹出"&#xff1f;为什么有些软件安装需要管理员权限&#xff1f;这些问题的答案都藏在文件系统接口的设计哲学里。作为用户与存储设备之间的桥…

作者头像 李华
网站建设 2026/7/17 20:24:34

HarmonyOS7 基础按钮展示卡片:用 Button 把按钮样式做得更顺手

文章目录前言这个案例适合怎么学从布局读到交互核心逻辑说明完整代码放到项目里要注意什么写在最后前言 这篇继续聊 HarmonyOS7 里的 Button 基础样式。如果你刚开始学 ArkUI&#xff0c;不建议一上来就背属性名&#xff0c;那样很容易看完就忘。更好的方式是先搞清楚&#xf…

作者头像 李华
网站建设 2026/7/17 20:16:57

索引失效的 10 种场景:MySQL EXPLAIN 逐一验证与根因分析

叙事框架&#xff1a;现象 → 排查过程 → 根因 → 修复 → 预防问题现象 MySQL 索引失效是导致慢查询最常见的原因之一。本文基于一个电商订单表的测试环境&#xff0c;系统验证了 10 种索引失效场景&#xff1a;隐式类型转换、函数操作列、左模糊匹配、OR 条件连接、联合索引…

作者头像 李华