【问题标题】:Singletons alternative单身替代品
【发布时间】:2012-11-29 15:17:12
【问题描述】:

我有一个非常简单的游戏引擎。它使用了几个单例(我会列举其中的一些)。

Resource Manager
Render Engine
Events Manager
Factory
etc

这些单例有很多从一个到另一个的调用。我将使用 Events Manager 示例:

  1. 任何从 Listener 派生的对象都可以添加 itsel 作为某些事件的侦听器,就像 EventsManager->RegisterListener(this, &SomeClass::SomeMethod); 一样(事件类型由 SomeMethod 参数推断)
  2. 任何其他对象都可以触发这样的事件EventsManager->PushEvent(SomeEvent);

经过一些同步后,事件到达所有侦听器。这是单例时 EventsManager 的一个非常简单的用法。

其他单身人士也有类似的行为。我想删除单例,但我的主要问题是我想从“用户的角度”保持代码简单易用,就像现在一样。我阅读了一些这样做的技术,但大多数使类的初始化/使用更加复杂。我知道这个话题在 SO 上被讨论过很多次,但没有一个答案适合我的编程理念——让一切尽可能简单。

我不想对我的类进行复杂的定义/初始化,例如:

SomeClass<EventManager, RenderEngine,...>

SomeClass::SomeClass(EventsManager, RenderEngine...)

你能就这个话题给我一些建议吗?

【问题讨论】:

  • 为什么要摆脱单例?
  • 我在想,与其在命名空间范围内拥有所有这些单例,不如在一个对象中拥有实例(其中通常只有一个,可能用于测试除外)。然后当你让它发挥作用时,考虑更多的分而治之。可能。
  • 是的,为什么要删除单例?它们可能是最合适的模式。
  • 按照您的理念,我一直认为,如果您想确保应用程序中只有一个对象实例,那么最简单的做法就是创建一个。
  • @giorashc 我对单例的最后一个问题是内存泄漏检测。我覆盖新/删除,我想在“最后”转储未删除的分配内存。你能告诉我在这种环境下什么时候“结束”吗?

标签: c++ oop design-patterns


【解决方案1】:

您可以有一个全局“游戏”对象,该对象为当前为单例的每个类创建一个实例

对于你的EventManager的具体例子;您的 Listener 基类可以提供派生类可以调用的注册方法和推送方法的实现。

骨架定义:

class Listener
{
public:
    virtual void ReceiveMessage( ... ) = 0;

protected:
    void Register()
    {
        GetEventManagerSomehow()->RegisterListener( this, etc );
    }

    void PushEvent( etc )
    {
        GetEventManagerSomehow()->PushEvent( etc );
    }

}

【讨论】:

  • “集线器”的想法非常适合我的问题。这样,我只有一个单例,即“集线器”,一切都在这里创建/销毁,并且可以通过“集线器”访问。结合 Mark Ransom 提议,我可以在“末尾”删除“集线器”,或者我可以确定在“集线器”析构函数中我没有任何全局变量。
【解决方案2】:

为了解决在你的单例中检测资源泄漏的具体问题,给每个单例类一个关闭方法来销毁实例。

class Singleton
{
    // ...
    static Singleton * GetInstance()
    {
        if (instance == NULL)
            instance = new Singleton;
        return instance;
    }
    static void Shutdown()
    {
        delete instance;
        instance = NULL;
    }
    static Singleton * instance;
};

Singleton * Singleton::instance = NULL;

【讨论】:

    【解决方案3】:

    不是真正的答案,但评论可能太长了。

    Dependency injection 是单例的一个很好的替代方案:您在程序的主函数中创建实例并将它们传递给“模块”(它们是主类),因此它们可以在本地使用它们。这意味着对于这些类,您将拥有您不想要的“复杂”构造函数。
    但是,复杂性应该仅限于某些类,并且在我看来,传递一些依赖的“模块”并不那么复杂。作为奖励,您可以通过查看构造函数或主函数来找出模块之间的依赖关系。

    依赖注入被大量使用,因为它确实解决了您所看到的问题(以及更多,例如单元测试),并且只增加了非常有限的复杂性。

    【讨论】:

    • 我在发布此问题之前阅读了有关依赖注入的信息,但我遇到了一个问题(再次事件管理器:P):如何使用来自任何模块的任何类的 PushEvent 而不向这些类发送 EventsManager 的引用?也许在这个模式中有一些我没有理解的东西
    • 最直接的方法是将引用传递给构造函数。但是,这应该只对一些主要类进行。较小的类可以根据需要从它们的“管理器类”中检索引用,因此不必对每个类进行注入。
    • 这会使代码不一致。在一类中必须使用 SomeManager1->GetEventManager->PushEvent,而在另一类中必须使用 SomeManager2->GetEventManager->PushEvent。这也将使某些类仅在代码的一部分中可用(“管理器类”不同”)。
    • 如果您从模块的角度来看,这并没有矛盾(程序分为包含自己的一组子模块和类的功能部分)。如果您的程序是单一的,并且您重视一致性而不是调制,那么可能有一个可以访问的主要类管理器(如 user1158692)。您还可以将依赖注入扩展到需要引用的每个类。这是一个设计选择,由您决定。
    猜你喜欢
    • 2012-10-09
    • 1970-01-01
    • 2012-06-13
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 1970-01-01
    • 2010-11-21
    • 1970-01-01
    相关资源
    最近更新 更多