设计模式之状态模式:面试官让我写机器人行为切换逻辑,我写了一堆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原则——面试官问"你怎么设计类的"评判标准
有任何问题欢迎评论区留言,我会尽量回复。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)