【问题标题】:Design pattern for factory of shared objects共享对象工厂的设计模式
【发布时间】:2018-01-03 15:02:24
【问题描述】:

我有一个系统,其中单例对象负责在其他非单例类之间创建和共享对象。例如:

// One instance is shared between multiple instances
// of Widget that have the same ID.
class Lease {};

// For each unique Widget instance that has the same
// value for `m_id`, the Controller will either
// create a new Lease (if one has not already been
// created) or it will return an existing one.
class Widget
{
private:
    unsigned m_id{};
    std::shared_ptr<Lease> m_lease;
};

// Singleton object that is used by all instances of
// Widget to obtain shared instances to Lease
class Controller
{
public:
    std::shared_ptr<Lease> CreateLease(unsigned id);

private:
    std::map<unsigned, std::weak_ptr<Lease>> m_leases;
};

这是我目前所拥有的要点。如果地图中不存在给定 ID 的新租约,Controller 所做的是创建一个新租约。如果确实存在,则返回现有的共享对象。

我有一个计时器,它会定期检查 m_leases 映射中的“过期”弱指针,如果是,将删除它们。这意味着在某些时候,多个小部件被销毁,随后也释放了它们的租约。

现在您已经有了一些背景知识,我想我在这里拥有的是一个比普通工厂更智能的工厂(控制器):它跟踪实例的创建,并且仅根据某些业务规则创建新实例(具体来说,如果在地图中找到匹配的 ID)。我不确定这是否是我正在尝试做的最佳设计(即:在 Widget 的唯一实例之间共享 Lease 实例的某种机制)。我不喜欢这个解决方案的几点:

  1. 需要单例作为 Widget 实例获取租约的联系点
  2. 使用计时器来管理地图:没有我能想到的基于事件的方法来管理从地图中删除过期租约。
  3. 继续 #2:因为我使用计时器来管理地图中的租约到期,所以即使租约到期,地图中也始终存在一个小窗口,这意味着 CreateLease() 也必须检查如果 weak_ptr 已过期,如果找到现有 ID 映射,则在返回之前。

我觉得这个逻辑是错误的。我需要对这个想法有更多的关注,最好是推荐更好的模式来解决这个问题。

【问题讨论】:

  • “我有一个计时器,它会定期检查 m_leases 映射中的“过期”弱指针,如果是,则将其删除。这意味着在某些时候,多个 Widget 被销毁了” i>:当您将weak_ptr 存储在map 中时,您的地图清理(删除nullptr weak_ptr)不会释放小部件。这只是对map 的优化。

标签: c++ design-patterns


【解决方案1】:

我会在析构函数中广播租约 - 观察者模式。

这将允许控制器注册一个观察者来删除租约并将其删除。这消除了您对计时器的需求,并引入了事件的概念;满足 2 和 3。

至于控制器是单例,从您发布的内容来看,它可以嵌套在Widget中并设为私有(或移动到Widget的cpp文件中)。虽然这最终仍将是一个单例,但由于其范围有限,它的可控性更高,并且可以在以后轻松替换。

如何组合在一起的示例:

class Lease {
public:
    struct listener {
        virtual void leaseGone(int id) = 0;
    }

    void addListener(listener* l) {
        listeners.push_back(l);
    }

    ~Lease() {
        for (auto x&: listeners)
            x->leaseGone(myId);
    }
 }


class Controller : Lease::listener {
    void leaseGone(int id) {
        m_leases.erase(id);
    }
}

【讨论】:

  • 我喜欢这个想法,但可能有未指明的行为:stackoverflow.com/questions/41851520/…
  • 我的编辑显示它不会命中这个 UB,因为它根本不测试弱指针的有效性——它知道它是无效的;所以不需要测试
  • 我刚才看到了你的例子。链接的 SO 仅因为我的租约地图使用了weak_ptr 而相关。在Lease 的析构函数中,shared_ptr 的use_count() 在检查weak_ptr::expired() 之前需要为0,这是我计划在removeLeaseFromMap() 的实现中进行的额外检查。但是仔细想想,这可能是不必要的,因为调用了 Lease 析构函数这一事实意味着 ref count 为 0,否则它不会被破坏。
  • 完全正确 - removeLeaseFromMap() 不需要做任何检查,因为它已经消失了。
  • 完美。很好的答案。感谢您提供示例代码。总结讨论,我建议您更新您的代码示例以显示removeLeaseFromMap 的实现,以显示只需要调用m_leases.erase(id);,而不检查weak_ptr::expired()。这是为了您的答案的未来观众的利益。再次感谢!!
猜你喜欢
  • 2010-09-06
  • 2011-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多