gpt4 book ai didi

c++ - C++ Singleton Design模式替代方案

转载 作者:塔克拉玛干 更新时间:2023-11-03 07:04:02 24 4
gpt4 key购买 nike

我讨厌打败一匹死马,也就是说,在过去几天中,关于单例模式的使用,我已经阅读了许多冲突的文章。

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

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

用例如下:

  • 可全局访问的记录器。
  • 一个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”的某些方面,但是您仍然无法将它逐字传递给任何地方,以防您可能想使用记录器调试某些东西,或者稍后再调试。在开发中,您实际上需要联网或该部分代码中的任何内容。

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

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

    我也是:
  • 在这些用例中坚持使用单例,无论人们怎么说“邪恶/坏”。
  • 将所有内容包装在上下文对象中,并按字面意义将其传递到任何地方。 (似乎有点海事组织,但如果那是“更容易接受/更好”的方式,那就这样吧。)
  • 完全不一样的东西。 (确实丢失了。)

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

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

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

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

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

    最佳答案

    每当您认为要使用Singleton时,请向自己提出以下问题:为什么要不惜一切代价确保在任何时候都不存在一个以上此类的实例?因为Singleton模式的整个要点是确保永远不会有一个以上Singleton实例。这就是“单例”一词的全部含义:只有一个。这就是为什么将其称为“单例”模式。这就是为什么该模式要求构造函数为私有(private)的原因。 Singleton模式的要点不是,也永远不会是为您提供全局可访问的某种事物的实例。唯一实例具有全局访问点的事实仅仅是Singleton模式的结果。 Singleton模式不是要达到的目标。如果您想要的只是某个事物的全局可访问实例,请使用全局变量。这正是全局变量的用途……
    单例模式可能是一种设计模式,这种模式经常被误解而不是被误解。网络连接这一概念的内在方面是一次只能有一个网络连接,如果违反该约束,世界将走向灭亡?如果答案是否定的,则没有理由将网络连接建模为Singleton。但是请不要相信我,通过查看第127页的《设计模式:最初描述了Singleton模式的可重用的面向对象软件的元素》来说服自己...😉
    关于您的示例:如果您最终不得不将大量参数传递到某个地方,那么首要的事情就是一件事:那个地方的职责太多了。使用Singletons不会改变这一事实。使用Singletons只会混淆这一事实,因为您不会被迫以参数的形式将所有东西通过一扇门进入,而是直接在整个地方直接访问所需的东西。但是您仍在访问这些东西。因此,您的代码片段的依赖关系是相同的。这些依赖关系只是在某些界面级别不再明确表达,而是在迷雾中四处蔓延。而且,您永远不会预先知道某个代码片段所依赖的东西,直到您尝试删除其他东西所依赖的东西之后您的构建中断。请注意,此问题并非特定于Singleton模式。总的来说,这是任何类型的全局实体都关心的问题……
    因此,您应该问一个问题,为什么这段代码需要访问那么多东西,而不是问如何最好地传递大量参数的问题?例如,您是否真的需要显式地将网络连接传递到游戏循环?如果游戏循环可能不仅仅知道物理世界对象,而且该物理世界对象在创建时已获得处理网络通信的某些对象。而那个对象又在初始化时告诉它应该使用的网络连接?日志可能只是一个全局变量(或者,关于日志本身的想法是否真的有什么禁止任何一个以上的日志的?)。或者,也许每个线程都拥有自己的日志(可能是线程局部变量)实际上是有意义的,以便您从每个线程获取日志的顺序是该线程恰好采取的控制流顺序,而不是一些(在最好)交错的困惑,它是您可能要为其编写一些工具的多个线程的输出,以便您至少有一点希望完全理解它……
    关于性能,请考虑一下,在游戏中,通常会有一些父对象,每个父对象管理小的子对象的集合。性能至关重要的东西通常会发生在必须对此类集合中的所有子对象进行某些处理的地方。首先到达父对象本身的相对开销通常应该可以忽略不计……
    PS:您可能想看看Entity Component System pattern

    关于c++ - C++ Singleton Design模式替代方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53977200/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com