我讨厌死马当活马医,也就是说,在过去的几天里,我已经阅读了很多关于单例模式使用的相互冲突的文章。
这个问题不是关于哪个是一般更好的选择,而是关于什么对我的用例有意义。
我正在做的宠物项目是一个游戏。我目前正在编写的一些代码,我倾向于使用单例模式。
用例如下:
- 全球可访问的记录器。
- OpenGL 渲染管理器。
- 文件系统访问。
- 网络访问。
- etc.
现在需要澄清的是,以上多个要求在访问之间共享状态。例如,记录器正在包装日志库并需要指向输出日志的指针,网络需要已建立的开放连接等。
现在据我所知,更建议避免单例,所以让我们看看我们如何做到这一点。许多文章只是说在顶部创建实例并将其作为参数传递到需要的任何地方。虽然我同意这在技术上是可行的,但我的问题是,如何管理潜在的大量参数?那么想到的是将不同的实例包装在一种“上下文”对象中并传递它,然后执行类似的操作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++ 程序员的思维方式/最佳实践,但正如我所说,我似乎无法找到直接的答案。我还注意到,建议仅传递“单例”作为参数的文章仅给出了非常简单的用例,任何人都同意参数将是更好的方法。
在此游戏示例中,即使您不打算立即使用日志记录,您也可能不想在任何地方访问日志记录。文件系统的东西可能已经全部结束了,但是在您构建出项目之前,很难说它何时何地最有用。
So do I:
- 无论人们说它多么“邪恶/坏”,都坚持在这些用例中使用单例。
- 将所有内容包装在上下文对象中,并将其逐字传递到任何地方。 (在我看来有点恶心,但如果这是“更容易接受/更好”的做法,那就这样吧。)
- 完全是另一回事。 (真的不知道那可能是什么。)
如果选项 1,从性能的角度来看,我是否应该切换到使用命名空间函数,并将“私有”变量/函数隐藏在匿名命名空间中,就像大多数人在 C 中所做的那样? (我猜性能会有一个小小的提升,但随后我将不得不对其中一些方法调用“init”和“destroy”方法,而不是仅仅允许构造函数/析构函数为我这样做,仍然值得吗?)
现在我意识到这可能有点基于意见,但我希望当出现更复杂/嵌套的代码库问题时我仍然可以获得相对好的答案。
Edit:经过深思熟虑,我决定改用“服务定位器”模式。为了防止服务定位器的全局/单例,我使任何可能使用服务的东西继承自抽象基类,该基类需要在构造时传递服务定位器。
我还没有实现所有功能,所以我仍然不确定这种方法是否会遇到任何问题,并且仍然希望得到关于这是否是单例/全局范围困境的合理替代方案的反馈。
我读过,服务定位器在某种程度上也是一种反模式,也就是说,我发现的许多示例都使用静态和/或作为单例来实现它,也许按照我所描述的方式使用它可以消除导致它的方面是一个反模式?