正确使用和实现单例

Posted

技术标签:

【中文标题】正确使用和实现单例【英文标题】:Using and Implementing a Singleton Correctly 【发布时间】:2012-12-07 07:23:47 【问题描述】:

我正在涉足编写多线程游戏结构。我下面是基本结构。 EventManager、LogicManager 和 Renderer 都是线程。他们从一个通用的 Gamestate 类中读取/写入,该类将处理线程之间的所有共享资源。据我了解,Gamestate 在技术上应该是一个单例。那是对的吗?我还想知道如何将其实现为“早期初始化单例”,如下所述:http://www.oodesign.com/singleton-pattern.html#early-singleton,C++ 除外。恐怕我不太精通 C++ 静态,因此我不知道将“私有静态 Singleton 实例 = new Singleton();”放在哪里C++ 中的行。我知道我可以通过解决方法获得相同的效果,但是我想尝试这种方式。

int main()
    Gamestate gs;
    EventManager em(&gs);
    LogicManager lm(&gs);
    Renderer renderer(&gs);

    lm.start();
    renderer.start();
    em.eventLoop();

    return 0;

【问题讨论】:

这里不需要单例。您的 main 创建了一个可以在所有线程中访问的对象。您真正要担心的是同步机制。 使用单例的最终最佳方法是:不要!单例模式被严重滥用为某种形式的被认为是有福的全局数据。它不是。正如 juanchopanza 所说,您的代码不使用单例,但是应该将其更改为使用单例。 您可能应该查看publisher-subscriber 和/或observer 模式,并完全避免共享数据。 “技术上应该是单身人士”——谁说的?您在这里拥有它的方式很好 - 制作一个并将其传递到需要的地方。 【参考方案1】:

正如@juanchopanza 和@Dietmar 评论的那样,游戏状态确实没有理由应该是单例。

此外,我能想到为什么它不应该是一个的几个原因:

    单例使单元测试变得非常困难。模拟单例比模拟接口要困难得多。

    假设有一天你想扩展你的游戏?例如,如果有一天你想让游戏逻辑在服务器上运行并让你的玩家运行客户端(想想多人游戏)怎么办?在这种情况下,您可能希望在您的流程中拥有多个游戏状态,而单例则不必要地限制您。

    如果你想对游戏状态做一些事情,比如序列化它以在机器之间发送,或者从文件保存和加载它,将游戏状态设置为单例会更加困难。使用标准的序列化框架变得很麻烦。

    很难将依赖项注入到单例中。如果你需要一些组件或数据来初始化你的游戏状态怎么办?在这种情况下,您将在一段时间内某个线程可能会访问游戏状态实例,即使它处于不一致的状态。

我可能会想到更多的原因。无论如何,我的建议是为所有组件定义接口,并让每个组件接收它所依赖的组件作为接口作为构造函数参数。

这使得编写良好解耦的类、防止类膨胀以及进行良好的单元测试变得更加容易。

然后您可以使用任何控制反转框架在游戏初始化代码中自动将所有组件绑定在一起。

【讨论】:

【参考方案2】:

如果你绝对需要一个全局的游戏状态对象,那就让它成为全局的。将其声明为全局(智能)指针,在main 开头手动初始化,最后手动销毁。通过这样做,你

    避免多线程问题 完全控制单例的初始化和销毁​​顺序 轻松注入模拟对象以进行单元测试

正如其他人指出的那样,单例被高估到成为反模式的地步。避免。

【讨论】:

以上是关于正确使用和实现单例的主要内容,如果未能解决你的问题,请参考以下文章

使用单例模式实现日志写入。附代码

8种单例模式的实现

如何实现与 ARC 兼容的 Objective-C 单例?

单例模式你真的会了吗(上篇)?

单例模式正确使用方式

正确使用 Alamofire 队列