我不想重复讲述已经说过的话,但是在过去几天里,我看了很多关于单例模式使用的文章,它们互相矛盾。
我的问题并不是哪种方法更好,而是要根据我的用例来选择哪种方法。
我正在开发一个游戏项目。目前我正在编写的一些代码,我倾向于使用单例模式。
以下是使用案例:
- 全局可访问的记录器。
- 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 ++程序员的思维方式/最佳实践,但是像我说的那样,我似乎找不到一个直截了当的答案。我还注意到,建议只将“单例”作为参数传递的文章只给出非常简单的用例,任何人都会同意参数是更好的方法。
在这个游戏示例中,即使您不打算立即使用它,您可能仍希望在任何地方都可以访问日志记录。文件系统的东西可能到处都是,但在构建项目之前,很难说它何时/何处最有用。
所以我应该:
1.无论人们如何说它是“邪恶/不好”,都坚持使用单例模式来处理这些用例。
2.将所有内容封装在上下文对象中,并在每个地方都传递它。 (在我看来有点恶心,但如果这是“更被接受/更好”的方法,那就这样吧。)
3.完全不同的东西。 (真的不知道那可能是什么。)
如果选择1,则从性能角度考虑,应该切换到使用命名空间函数,并在匿名命名空间中隐藏“私有”变量/函数,就像大多数人在C中所做的那样? (我猜性能会略有提高,但然后我将被迫在一些这些上调用“init”和“destroy”方法,而不能让构造函数/析构函数为我执行这些操作,这仍然可能值得一试?)
现在我意识到这可能有点基于观点,但我希望在涉及更复杂/嵌套的代码库时仍然可以获得相对较好的答案。
编辑:经过更多的商议,我决定改用“服务定位器”模式。为了防止服务定位器的全局/单例出现,我使可能使用服务的任何内容都继承自要求在构造时传递服务定位器的抽象基类。
我还没有实现所有内容,因此我仍然不确定是否会遇到任何问题,仍然希望获得反馈,以确定这是否是单例/全局范围困境的合理替代方案。
我已经阅读过服务定位器也是一种反模式,但是,我找到的许多示例都使用静态和/或单例来实现它,也许像我描述的那样使用它会消除导致它成为反模式的方面?