设计模式之观察者与策略:面试官问“ROS2回调机制用的什么模式”,我没答上来
上篇聊了单例和工厂模式,今天继续设计模式专题,聊聊观察者模式和策略模式。
这两个模式在机器人开发里特别常见——ROS2的回调机制就是观察者模式,机器人的行为切换就是策略模式。但面试的时候,很多人知道怎么用,却说不出背后的设计模式。
面试官问"ROS2的回调机制用的什么设计模式?"如果你能直接回答"观察者模式",并且讲清楚为什么,面试官对你的评价会高一个档次。
观察者模式:发布-订阅的本质
观察者模式的定义:对象之间一对多的依赖关系。当一个对象状态改变时,所有依赖它的对象都会收到通知。
ROS2的发布-订阅机制就是这个模式的实现。话题(Topic)是被观察的对象,订阅者是观察者,发布者负责通知。
用C++实现一个简单的观察者模式:
#include <functional>
#include <vector>
#include <string>
// 观察者接口
template<typename EventType>
class Observer {
public:
virtual void onEvent(const EventType& event) = 0;
virtual ~Observer() = default;
};
// 被观察者(主题)
template<typename EventType>
class Subject {
std::vector<Observer<EventType>*> observers_;
public:
void subscribe(Observer<EventType>* observer) {
observers_.push_back(observer);
}
void unsubscribe(Observer<EventType>* observer) {
observers_.erase(
std::remove(observers_.begin(), observers_.end(), observer),
observers_.end());
}
void notify(const EventType& event) {
for (auto* observer : observers_) {
observer->onEvent(event);
}
}
};
在机器人系统里,激光雷达数据发布后,可能有多个模块需要接收:导航模块要用来避障,建图模块要用来更新地图,显示模块要用来可视化。这就是典型的观察者模式。
策略模式:让行为可以动态切换
策略模式的定义:定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换。
在机器人开发里,最常见的场景是导航策略的切换。比如机器人在不同环境下用不同的导航策略:开阔地带用直线导航,狭窄通道用慢速精确导航,人多的地方用保守导航。
// 策略接口
class NavigationStrategy {
public:
virtual void navigate(const Pose& current, const Pose& target) = 0;
virtual ~NavigationStrategy() = default;
};
// 具体策略
class FastNavigation : public NavigationStrategy {
public:
void navigate(const Pose& current, const Pose& target) override {
// 直线路径规划,速度优先
cout << "Fast navigation to target" << endl;
}
};
class CautiousNavigation : public NavigationStrategy {
public:
void navigate(const Pose& current, const Pose& target) override {
// 保守路径规划,安全优先
cout << "Cautious navigation to target" << endl;
}
};
// 上下文:机器人控制器
class RobotController {
unique_ptr<NavigationStrategy> strategy_;
public:
void setStrategy(unique_ptr<NavigationStrategy> strategy) {
strategy_ = std::move(strategy);
}
void update(const Pose& current, const Pose& target) {
strategy_->navigate(current, target);
}
};
使用的时候:
RobotController controller;
// 开阔地带,用快速导航
controller.setStrategy(make_unique<FastNavigation>());
controller.update(current_pose, target_pose);
// 进入狭窄区域,切换到保守导航
controller.setStrategy(make_unique<CautiousNavigation>());
controller.update(current_pose, target_pose);
策略模式的好处是把算法的定义和使用分开。新增一种导航策略,不需要改RobotController的代码。
观察者模式在ROS2中的实际应用
ROS2的回调函数本质上就是观察者模式。你创建一个订阅者,注册一个回调函数,当消息到达时,回调函数被调用。
class LidarProcessor : public rclcpp::Node {
public:
LidarProcessor() : Node("lidar_processor") {
subscription_ = this->create_subscription<sensor_msgs::msg::LaserScan>(
"scan", 10,
[this](const sensor_msgs::msg::LaserScan::SharedPtr msg) {
processScan(msg);
});
}
private:
void processScan(const sensor_msgs::msg::LaserScan::SharedPtr& msg) {
// 处理激光雷达数据
}
rclcpp::Subscription<sensor_msgs::msg::LaserScan>::SharedPtr subscription_;
};
这里的Lambda表达式就是观察者。它观察"scan"这个话题,当有新消息时,被通知执行。
ROS2的回调组(Callback Group)也是观察者模式的延伸。你可以把多个回调放在同一个组里,控制它们的执行顺序和并发方式。
面试中的考点
"观察者模式和发布-订阅模式有什么区别?"严格来说,发布-订阅是观察者模式的变体。经典观察者模式里,观察者和被观察者是紧耦合的;发布-订阅模式通过中间的消息代理解耦了两者。ROS2用的是发布-订阅模式。
"策略模式和状态模式有什么区别?"策略模式是让用户选择算法,状态模式是让系统根据当前状态自动切换行为。策略模式的切换是主动的,状态模式的切换是被动的。这个区别面试经常考。
"观察者模式有什么性能问题?"如果观察者很多,通知的开销会很大。而且观察者的执行顺序不确定,如果观察者之间有依赖关系,可能出问题。在机器人系统里,通常用优先级队列来控制通知顺序。
给正在准备面试的你
观察者模式和策略模式在机器人开发里用得非常多。面试的时候,能把设计模式和实际项目结合起来讲,比单纯背定义强得多。
比如你可以说:"我们的导航系统用策略模式实现了多种导航算法的切换,用观察者模式实现了传感器数据的分发。"这比"我知道观察者模式是什么"有说服力得多。
下篇聊状态模式——机器人行为管理的优雅方案。在聊状态模式之前,再提醒一点:观察者模式在多线程环境下的线程安全问题经常被忽略。如果主题和观察者在不同线程,通知时必须加锁或者用线程安全的消息队列,否则很容易出竞态条件导致的bug。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。
「机器人软件开发面试·从入门到精通」连载系列 上一篇:第46篇 设计模式之单例与工厂——机器人传感器管理中的实际应用 下一篇预告:第48篇 设计模式之状态模式——机器人行为管理的优雅方案
有任何问题欢迎评论区留言,我会尽量回复。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)