C ++单例设计模式替代方案

时间:2018-12-30 11:24:52

标签: c++ design-patterns singleton

我讨厌打败一匹死马,也就是说,过去几天,我在使用单例模式方面已经阅读了许多相互矛盾的文章。

这个问题不是关于总体上哪个是更好的选择,而是关于我的用例是什么。

我正在从事的宠物项目是一个游戏。我目前正在处理的一些代码,倾向于使用单例模式。

用例如下:

  • 可全局访问的记录器。
  • OpenGL渲染管理器。
  • 文件系统访问。
  • 网络访问。

现在为澄清起见,以上两个以上要求访问之间具有共享状态。例如,记录器包装了一个日志库,需要一个指向输出日志的指针,网络需要一个已建立的开放连接,等等。

现在,据我所知,更建议避免单身人士,因此让我们看看如何做到这一点。许多文章只是简单地说在顶部创建实例,并将其作为参数传递到所需的任何位置。尽管我同意这在技术上是可行的,但我的问题变成了,如何管理潜在的大量参数?好吧,我想到的是将不同的实例包装在一种“上下文”对象中,并传递该对象,然后执行类似context->log("Hello World")的操作。现在确保这还不错,但是如果您有这样的框架呢?

game_loop(ctx)
   ->update_entities(ctx)
        ->on_preupdate(ctx)
             ->run_something(ctx)
                 ->only use ctx->log() in some freak edge case in this function.
        ->on_update(ctx)
            ->whatever(ctx)
                 ->ctx->networksend(stuff)
   ->update_physics(ctx)
        ->ctx->networksend(stuff)
        //maybe ctx never uses log here.

您明白了……在某些方面,从未使用过“ ctx”的某些方面,但是您仍然无法将它逐字传递给任何地方,以防您可能想使用logger调试某些东西,或可能在以后的开发中,您实际上想要联网或该部分代码中的任何内容。

我觉得上面的示例非常适合于全局可访问的单例,但是我必须承认,我来自C#/ Java / JS背景,可能会使我的观点成色。我想采用C ++程序员的心态/最佳实践,但是就像我说的那样,我似乎找不到一个直接的答案。我还注意到,建议仅将“ singleton”作为参数传递的文章仅给出非常简单的用例,任何人都同意参数将是更好的选择。

在此游戏示例中,即使您不打算立即使用它,也可能无法访问所有地方的日志记录。文件系统的内容可能无处不在,但是在您构建项目之前,很难说出何时/何地最有用。

我也是这样:

  1. 不管人们怎么说“邪恶/坏”,都坚持在这些用例中使用单例。
  2. 将所有内容包装在上下文对象中,并按字面意义将其传递到任何地方。 (似乎有点过分的IMO,但是如果那是“更容易接受/更好”的方式,那就来吧。)
  3. 完全不一样的东西。 (真的迷失了。)

从性能的角度来看,如果选择1,我应该切换到使用名称空间函数,并像大多数人在C语言中一样在匿名名称空间中隐藏“私有”变量/函数吗? (我猜想性能会有小幅提升,但随后我将不得不在其中一些方法上调用“ init”和“ destroy”方法,而不是仅仅允许构造函数/析构函数这样做对我来说还是值得的?)

现在我意识到这可能是基于观点的,但是我希望当有一个更复杂/嵌套的代码库出现问题时,我仍然可以获得一个相对不错的答案。

编辑: 经过深思熟虑,我决定改为使用“服务定位器”模式。为了防止全局/单个的Service Locator,我正在做任何可能使用从抽象基类继承的服务的事情,该抽象基类要求在构造时传递Service Locator。

我还没有实现所有事情,所以我仍然不确定是否会遇到这种方法的任何问题,并且仍然希望获得反馈,以了解这是否可以替代单例/全局范围难题。

我已经了解到Service Locator在某种程度上也是一种反模式,也就是说,我发现的许多示例都是通过静态和/或单例实现的,也许就像我所描述的那样使用它消除了某些方面导致它成为反模式?

1 个答案:

答案 0 :(得分:0)

每当您认为要使用Singleton时,请向自己提出以下问题:为什么要不惜一切代价确保在任何时候都不存在一个以上此类的实例?因为Singleton模式的整个要点就是要确保Singleton实例不能超过一个。这就是“单身”一词的全部含义:只有一个。这就是为什么将其称为Singleton模式。这就是为什么该模式要求构造函数是私有的。 Singleton模式的重点是 not ,并且从来没有为您提供全局访问的实例。唯一实例具有全局访问点的事实仅仅是Singleton模式的结果。 Singleton模式不是要达到的目标。如果您想要的只是某个事物的全局可访问实例,请使用全局变量。这正是全局变量的用途……

Singleton模式可能是一种设计模式,这种模式经常被误解而不是被误解。网络连接这一概念的内在方面是一次只能有一个网络连接,如果违反该约束,世界将走向灭亡?如果答案是否定的,则没有理由将网络连接建模为Singleton。但是请不要相信我,请检查一下第127页(最初是否引入了Singleton模式)的设计模式:可重用的面向对象软件的元素来说服自己。 / p>

关于您的示例:如果您最终不得不将大量参数传递到某个地方,那么首要的是告诉您一件事:在那个地方有太多的责任。使用Singletons不会改变这一事实。使用Singletons只会混淆这一事实,因为您不必被迫以参数的形式将所有东西通过一扇门进入,而是直接在整个地方直接访问您想要的任何东西。但是您仍在访问这些东西。因此,您的代码片段的依赖关系是相同的。这些依赖关系只是在某些界面级别不再明确表达,而是在迷雾中四处蔓延。而且,您永远都不知道某段代码所依赖的东西,直到您尝试删除其他东西所依赖的东西之后,构建中断。请注意,此问题并非特定于Singleton模式。总的来说,这是任何一种全球实体所关心的问题……

因此,您应该问一个问题,为什么这段代码需要访问那么多东西,而不是问如何最好地传递大量参数?例如,您是否真的需要显式地将网络连接传递给游戏循环?如果游戏循环可能不仅仅知道物理世界对象,而且该物理世界对象在创建时已获得处理网络通信的某些对象。而那个对象又在初始化时告诉它应该使用的网络连接?日志可能只是一个全局变量(或者,关于日志本身的想法是否真的有任何东西禁止任何一个以上的日志?)。或者,也许每个线程都有自己的日志(可能是线程局部变量)实际上是有意义的,以便您从每个线程获取日志的顺序是该线程恰好采取的控制流顺序,而不是某些线程(在最好)交错的混乱,它是您可能想要为其编写一些工具的多个线程的输出,以便至少有一点希望完全理解它……

关于性能,请考虑在游戏中,通常会有一些父对象,每个父对象管理小的子对象的集合。对于性能至关重要的东西通常会发生在必须对此类集合中的所有子对象进行某些处理的地方。首先到达父对象本身的相对开销通常应该可以忽略不计……

PS:您可能想看看Entity Component System pattern