设计模式之单例与工厂:面试官让我设计一个传感器管理系统,我用单例被怼了
从这篇开始进入设计模式专题。C面试里设计模式考得不少尤其是做机器人开发的——面试官想看你能不能把代码组织好。先聊两个最基础的单例模式和工厂模式。面试的时候面试官说设计一个传感器管理系统管理不同类型的传感器。我上来就写了个单例。面试官说为什么用单例我说因为传感器管理器全局只需要一个。面试官追问单例有什么缺点我当时答得不好。今天把这两个模式掰开了讲。单例模式全局唯一实例单例模式保证一个类只有一个实例并提供全局访问点。class SensorManager { static SensorManager* instance_; SensorManager() {} // 私有构造 public: static SensorManager* getInstance() { if (!instance_) { instance_ new SensorManager(); } return instance_; } // 删除拷贝构造和赋值 SensorManager(const SensorManager) delete; SensorManager operator(const SensorManager) delete; }; SensorManager* SensorManager::instance_ nullptr; // 使用 auto* mgr SensorManager::getInstance();这个经典写法有个问题线程不安全。两个线程同时调用getInstance()可能创建两个实例。线程安全的版本用Meyers单例C11后推荐class SensorManager { public: static SensorManager getInstance() { static SensorManager instance; // 局部静态变量线程安全 return instance; } private: SensorManager() {} }; // 使用 auto mgr SensorManager::getInstance();C11保证局部静态变量的初始化是线程安全的。这是最简单也最推荐的单例实现。但单例有个很大的问题它本质上是全局变量。全局变量的问题它都有——难以测试、隐藏依赖关系、生命周期不可控。面试官追问单例有什么缺点的时候正确答案是单例让代码之间的依赖关系变得不透明。你调用SensorManager::getInstance()看函数签名完全不知道它依赖了SensorManager。而且单例的生命周期跟程序一样长析构顺序在程序退出时很难控制。现代C的趋势是少用单例多用依赖注入——把对象通过参数传进去而不是让它自己去取全局实例。工厂模式创建对象的正确姿势工厂模式解决的是怎么创建对象的问题。最简单的形式是简单工厂class SensorBase { public: virtual void read() 0; virtual ~SensorBase() default; }; class LidarSensor : public SensorBase { public: void read() override { cout Reading lidar endl; } }; class IMUSensor : public SensorBase { public: void read() override { cout Reading IMU endl; } }; class CameraSensor : public SensorBase { public: void read() override { cout Reading camera endl; } }; // 简单工厂 class SensorFactory { public: static unique_ptrSensorBase create(const string type) { if (type lidar) return make_uniqueLidarSensor(); if (type imu) return make_uniqueIMUSensor(); if (type camera) return make_uniqueCameraSensor(); return nullptr; } }; // 使用 auto sensor SensorFactory::create(lidar); sensor-read();简单工厂的问题是每加一种传感器都要改工厂的create方法。违反了开闭原则。改进的方式是用注册机制——每种传感器自己注册创建函数工厂只需要查表就行。新增传感器类型不需要改工厂代码。// 注册机制用map存储创建函数 mapstring, functionunique_ptrSensorBase() registry; registry[lidar] []() { return make_uniqueLidarSensor(); }; registry[imu] []() { return make_uniqueIMUSensor(); }; // 新增类型只需注册 registry[camera] []() { return make_uniqueCameraSensor(); }; auto sensor registry[camera]();在机器人开发里工厂模式用得非常多。ROS2里的组件加载机制本质上就是工厂模式——根据类型名创建对应的组件实例。单例工厂的配合使用实际项目里单例和工厂经常配合使用——一个全局的传感器工厂管理所有类型的创建。不过讲真如果不是特别复杂的系统我倾向于不用单例。直接把工厂对象作为参数传递代码更清晰也更好测试。面试中的考点单例怎么保证线程安全C11之后用Meyers单例局部静态变量编译器保证初始化线程安全。老式写法需要加mutex。工厂模式和new有什么区别new创建的是具体类型工厂创建的是抽象接口。工厂让你面向接口编程新增类型不需要改使用方的代码。什么时候用单例什么时候用依赖注入如果确实需要全局唯一的资源比如日志系统单例可以接受。但大部分情况下依赖注入更好——通过构造函数把依赖传进来代码的可测试性和可维护性都更高。再聊一个工厂模式的进阶用法自注册工厂。传统工厂模式的问题是每新增一个产品类型都要修改工厂类的if-else或者switch违反开闭原则。自注册工厂的做法是让每个产品类在加载时自动向工厂注册自己的创建函数。具体实现是用一个全局的map存储类型名到创建函数的映射每个产品类在静态初始化时把自己的创建函数注册进去。这样新增产品时只需要写一个新类工厂类完全不需要改动。在机器人开发里插件系统经常用这种模式——每个插件自动注册主程序通过名字动态加载。面试时如果你能讲出自注册工厂的实现思路面试官会认为你对设计模式的理解已经超越了教科书层面。给正在准备面试的你单例和工厂是设计模式里最基础的两个。面试里考的不只是你会不会写更是你知不知道什么时候该用、什么时候不该用。能讲清楚单例的缺点、工厂的扩展性优势、以及依赖注入的替代方案面试官会觉得你不只是在套模板而是真正理解了设计背后的权衡。这比会写代码本身更重要。补充一个面试技巧面试官问设计模式时不要只答单例和工厂。你可以主动提到在实际项目里我们还用了策略模式来处理不同的传感器数据处理算法用观察者模式来实现事件通知。这样面试官会觉得你的模式储备不止两个项目经验也更丰富。当然前提是你确实了解这些模式的原理和应用场景别给自己挖坑。另外提一点面试时聊设计模式最好结合具体代码来讲。比如我们的传感器管理模块用了工厂模式不同类型的传感器通过统一的接口创建新增传感器类型只需要加一个工厂方法不需要改调用方的代码。这种有细节的回答比泛泛而谈我知道单例模式有说服力得多。下篇聊观察者模式和策略模式——ROS2回调与行为切换中的实际应用。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第45篇 CRTP模式——静态多态的实现技巧 下一篇预告第47篇 设计模式之观察者与策略——ROS2回调与行为切换有任何问题欢迎评论区留言我会尽量回复。

相关新闻

最新新闻

日新闻

周新闻

月新闻