Hex*_*own 5 c++ singleton design-patterns
我讨厌打败一匹死马,也就是说,在过去的几天里,我已经阅读了很多关于使用单例模式的相互矛盾的文章。
这个问题不是一般来说哪个是更好的选择,而是什么对我的用例有意义。
我正在做的宠物项目是一个游戏。我目前正在处理的一些代码,我倾向于使用单例模式。
用例如下:
现在澄清一下,以上几个要求在访问之间共享状态。例如,记录器正在包装一个日志库并需要一个指向输出日志的指针,网络需要一个已建立的开放连接等。
现在,据我所知,更建议避免单身人士,所以让我们看看我们如何做到这一点。很多文章只是说在顶部创建实例并将其作为参数传递到需要的任何地方。虽然我同意这在技术上是可行的,但我的问题就变成了,如何管理潜在的大量参数?好吧,我想到的是将不同的实例包装在一种“上下文”对象中并传递它,然后执行类似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.
Run Code Online (Sandbox Code Playgroud)
你明白了......在某些领域,“ctx”的某些方面从未使用过,但你仍然坚持在任何地方传递它,以防你可能想使用记录器调试某些东西,或者稍后在开发中,您实际上需要网络或该部分代码中的任何内容。
我觉得上面的例子更适合全局可访问的单例,但我必须承认,我来自 C#/Java/JS 背景,这可能会影响我的观点。我想采用 C++ 程序员的心态/最佳实践,但就像我说的那样,我似乎找不到直接的答案。我还注意到,那些建议只将“单例”作为参数传递的文章只给出了非常简单的用例,任何人都会同意参数是更好的方法。
在这个游戏示例中,即使您不打算立即使用它,您也可能不想在任何地方访问日志记录。文件系统的东西可能已经结束了,但在你构建项目之前,很难说它何时/何地最有用。
我也是:
如果选项 1,从性能的角度来看,我应该切换到使用命名空间函数,并将“私有”变量/函数隐藏在匿名命名空间中,就像大多数人在 C 中所做的那样?(我猜性能会有小幅提升,但随后我将不得不在其中一些方法上调用“init”和“destroy”方法,而不能只允许构造函数/析构函数为我做这件事,仍然值得吗?)
现在我意识到这可能有点基于意见,但我希望当遇到更复杂/嵌套的代码库时,我仍然可以得到一个相对较好的答案。
编辑: 经过深思熟虑,我决定改用“服务定位器”模式。为了防止服务定位器的全局/单例,我正在制作任何可能使用从抽象基类继承的服务的东西,该抽象基类要求在构造时传递服务定位器。
我还没有实现所有的东西,所以我仍然不确定我是否会遇到这种方法的任何问题,并且仍然会喜欢关于这是否是单例/全局范围困境的合理替代方案的反馈。
我读过 Service Locator 也有点反模式,也就是说,我发现的许多示例都是用静态和/或作为单例实现的,也许像我所描述的那样使用它会删除导致它是一个反模式?
每当您认为要使用单例时,请问自己以下问题:为什么必须不惜一切代价确保在任何时间点都不会存在多个此类实例?因为单例模式的重点是确保单例的实例永远不会超过一个。这就是“单身”一词的全部含义:只有一个。这就是为什么它被称为单例模式。这就是为什么模式要求构造函数是私有的。单例模式的重点不是并且永远不会给你一个全局可访问的实例。存在到唯一实例的全局访问点的事实只是单例模式的结果。这不是单例模式要实现的目标。如果您想要的只是某个东西的全局可访问实例,那么请使用全局变量。这正是全局变量的用途……
单例模式可能是最常被误解的一种设计模式。一次只能有一个网络连接,如果违反了这个限制,世界就会走到尽头,这是否是网络连接概念的一个内在方面?如果答案是否定的,那么就没有理由将网络连接建模为单例。但是不要相信我的话,请查看设计模式:可重用面向对象软件的元素的第 127 页说服自己,其中最初描述了单例模式……
关于你的例子:如果你最终不得不将大量参数传递到某个地方,那么首先告诉你一件事:那个地方有太多的责任。单例的使用不会改变这一事实。单例的使用只是混淆了这一事实,因为您不必以参数的形式通过一扇门传递所有内容,而只是在整个地方直接访问您想要的任何内容。但是你仍然在访问这些东西。所以你的代码段的依赖是相同的。这些依赖关系在某些界面级别不再明确表达,而是在迷雾中蔓延。并且您永远不会预先知道某段代码所依赖的内容,直到您的构建在尝试删除其他东西碰巧依赖的东西后中断的那一刻。请注意,此问题并非特定于单例模式。一般来说,这是任何类型的全球实体都关心的问题……
因此,与其问如何最好地传递大量参数的问题,不如问一个问题,为什么这段代码需要访问这么多东西?例如,你真的需要明确地将网络连接传递给游戏循环吗?如果游戏循环不应该只知道物理世界对象,并且在给定处理网络通信的某个对象的情况下,该物理世界对象是在创建的那一刻。而那个对象又在初始化时告诉它应该使用的网络连接?日志可能只是一个全局变量(或者是否真的有任何关于日志本身的想法禁止存在多个日志?)。
关于性能,请考虑到,在游戏中,您通常会有一些父对象,每个父对象都管理小型子对象的集合。性能关键的事情通常发生在必须对此类集合中的所有子对象执行某些操作的地方。首先到达父对象本身的相对开销通常应该可以忽略不计......
PS:你可能想看看实体组件系统模式……