共享对象工厂设计模式优化咨询及替代方案推荐
首先得说,你的现有方案已经精准实现了核心需求——让同ID的Widget共享Lease实例,而且用weak_ptr避免了内存泄漏,整体思路是站得住脚的。不过你提到的三个痛点确实有优化空间,咱们逐一拆解聊聊:
现有方案的优缺点
优点
- 完美匹配业务逻辑:通过ID映射机制,确保同ID的
Widget能复用同一个Lease - 内存安全:用
weak_ptr跟踪实例生命周期,避免Lease被无用引用长期占用
缺点
- 强耦合问题:
Widget硬依赖Controller单例,不仅扩展受限,单元测试也很难做 - 定时清理的低效与冗余:轮询式的定时任务不是最高效的资源管理方式,还导致
CreateLease必须额外检查weak_ptr有效性,逻辑不够简洁
针对痛点的具体优化方案
1. 消除单例依赖:用依赖注入替代硬编码单例
单例最大的问题是把Widget和Controller死死绑定在一起。解决办法很简单:把Controller作为参数注入到Widget的构造函数里,而不是让Widget直接访问单例。
示例代码:
class Widget { private: unsigned m_id{}; std::shared_ptr<Lease> m_lease; public: // 构造时传入Controller,摆脱单例依赖 Widget(unsigned id, Controller& controller) : m_id(id), m_lease(controller.CreateLease(id)) {} };
这样Controller可以是全局对象、由上层容器管理,甚至测试时能传入Mock实现,灵活性提升一大截。
2. 事件驱动的过期条目移除:用shared_ptr自定义删除器替代定时任务
你头疼的定时清理问题,其实可以用shared_ptr的自定义删除器彻底解决。当最后一个持有Lease的shared_ptr被销毁时,删除器会自动触发,我们让它通知Controller从映射表中移除对应ID的条目——完全是事件驱动,没有时间窗口问题,也不需要轮询。
修改后的Controller实现(注意加上线程安全保护):
#include <mutex> #include <map> #include <memory> class Lease {}; class Controller { public: std::shared_ptr<Lease> CreateLease(unsigned id) { std::lock_guard<std::mutex> lock(m_mutex); auto it = m_leases.find(id); if (it != m_leases.end()) { // 这里的weak_ptr一定有效,因为实例销毁时条目已被移除 return it->second.lock(); } // 创建Lease并绑定自定义删除器 auto raw_lease = new Lease(); std::shared_ptr<Lease> shared_lease(raw_lease, [this, id](Lease* ptr) { delete ptr; // Lease销毁时自动从映射表中移除对应ID std::lock_guard<std::mutex> lock(m_mutex); m_leases.erase(id); }); m_leases[id] = shared_lease; return shared_lease; } private: std::mutex m_mutex; // 线程安全保护,多线程场景必须加 std::map<unsigned, std::weak_ptr<Lease>> m_leases; };
这个改动后,定时任务可以直接删掉——m_leases里永远只有有效的weak_ptr,不会存在过期条目。
3. 简化CreateLease逻辑:无需额外检查weak_ptr过期
因为用了自定义删除器,m_leases中的条目只会在Lease存活时存在。所以当CreateLease找到对应ID的条目时,weak_ptr一定能lock成功,直接返回即可,彻底去掉了冗余的有效性检查,逻辑瞬间简洁优雅。
推荐的设计模式:享元模式(Flyweight Pattern)
你的场景完全符合享元模式的定义:共享细粒度对象,通过工厂(这里的Controller)管理共享实例,根据唯一标识(ID)复用对象。享元模式的核心是分离内部状态(Lease本身的共享状态)和外部状态(Widget的私有状态),正好匹配你“同ID共享Lease”的需求。
优化后的方案就是享元模式的标准实现,结合C++智能指针特性,完美解决了生命周期管理的问题。
内容的提问来源于stack exchange,提问作者void.pointer

