【问题标题】:Is it good programming to have lots of singleton classes in a project?在一个项目中有很多单例类是好的编程吗?
【发布时间】:2010-01-17 06:54:44
【问题描述】:

我在一个项目中有几个类应该只创建一次。

最好的方法是什么?,

  1. 可以将它们创建为静态对象。
  2. 可以创建为单例
  3. 可以创建为全局。

实现这一点的最佳设计模式是什么?

我正在考虑将所有类创建为单例,但这会创建很多单例。有很多单例是一种好的编程习惯吗?

使用单例的优缺点是什么?

【问题讨论】:

  • 为什么只创建一次?
  • 很多单例有时可能表明设计不佳;也称为“代码气味”
  • 您应该将它们作为指向这些线程的指针传递。如果它们没有必须是全局的,请不要将它们设为单例。
  • @jalf,这门课呢? struct uhoh { uhoh(){static bool firstTime = true; if (!firstTime) make_computer_splode(); firstTime = false; } }; :)

标签: c++ singleton


【解决方案1】:

看看 Steve Yegge 的博客文章 - Singleton Considered Stupid

【讨论】:

  • 好的,我刚刚浏览了博客。。我遇到了类似的问题,比如 RegistryManager、FileManager、DataBaseManager 等类,每个类应该只初始化一次,不同的线程可以设置这些类的状态这应该对所有人都可见。
  • 感谢您的链接。那是一个很好的总结,并为我节省了很多话。有时单例是不可避免的邪恶,但应尽可能避免,尤其是当它们是可变的时。
【解决方案2】:

如果它们只需要创建一次,那并不要求它们应该是单例。

  • 如果 X 是单例,则暗示存在一个实例。
  • 如果 X 有一个实例,这并不意味着它应该是一个单例。

如果你要求只有一个类的实例,并且它是全局可访问的,则使用单例。在你的情况下,仅仅需要一个是不够的。全局变量是坏的,单例是被美化的全局变量。

大多数情况下,您不需要它们。由于这种心态,你会在糟糕的代码中看到很多:我只需要一个,这一定意味着我应该让它成为单例! ()例如,我已经完成了迄今为止我做过的最强大的游戏引擎的技术设计。它有 2 个单例,用于内存和线程。一个非常大的项目,我只有两个!

更多上下文将帮助我们为您提供更好的信息。

【讨论】:

  • 同意,虽然我不会称单身人士为“美化的全球人”。它们是全局变量,其中包含一个额外的不必要且通常是严重的设计问题。如果您需要全局可访问的东西,请将其设为全局变量,而不是单例。
  • @jalf:我不同意这一点。可以延迟创建单例,以避免静态失败。
  • 然后你会得到一个更复杂的实现来完全实现普通全局所提供的功能。仅仅因为可以解决单例创建的问题并不意味着它是比不首先创建问题更好的解决方案。单例对全局有什么好处?
  • @jalf,和上面一样,全局变量没有设置初始化顺序。单例也是类,具有私有的细节和功能。 (全局意味着更多的全局变量,而单例在技术上只是一个变量。)我不确定我是否理解单例对全局有什么问题。 :) 我的单身人士很简单:singleton<foo>::reference().foo_bar();,而且是非侵入性的。
  • 单例对全局的问题是你只能创建一个实例。当你想测试它时怎么办?祝你好运在你的测试用例设置中实例化它。当你发现哎呀,你确实毕竟需要两个。对您的代码施加“只能创建一个实例”是一种愚蠢且过早的约束。如果你想要延迟实例化的全局变量,那么就实现它们,没有什么能阻止你。但是不要费心阻止类的多个(非全局)实例化。
【解决方案3】:

我建议您查看 Google 的 Miško Hevery 制作的一些视频和文章。首先是一个视频:"Clean Code Talks: Global State and Singletons"his blog

普遍的共识是,在极少数情况下,单例是可以的,例如日志记录,但在大多数其他情况下,您希望使用依赖注入。单例使测试您的代码变得更加困难,并且它们隐藏了依赖项,因此您的类无法在孤立的情况下轻松实例化。

【讨论】:

    【解决方案4】:

    Singleton 存在一些问题——它们难以测试、难以替换和难以扩展。通常有更好的方法。

    【讨论】:

      【解决方案5】:

      我最喜欢的关于单例的文章之一是 Miško Hevery 的 Singletons are Pathological Liars。从本质上讲,它们鼓励了难以学习和测试的“隐藏”行为。

      【讨论】:

        【解决方案6】:

        有些项目实际上无法避免使用全局变量。各种服务定位器或依赖注入框架仍然依赖于对象的全局(不总是静态变量,但总是某种全局)存储。

        然而,单例是一个问题的征兆:

        • 首先,作为规范模式的单例模式在接口和抽象方面表现不佳。不过可以修复 - 通过工厂访问它。
        • 更糟糕的是,单例是不灵活的——除了类型之外,它们没有任何识别对象的方法。 (嗯,在 C++ 中,它们是通过模板完成的,但这是另一回事)。从这个意义上说,它们实际上比静态变量更糟糕。从长远来看,使用可以访问许多相同类型实例的框架是值得的。
        • 最重要的是,大量单例意味着对象之间存在大量遥远的关系。这意味着您的系统可能比它需要的更复杂,并且更难开发、测试和管理。简单地切换到定位器或 DI 对此无济于事,这是基本设计原则的问题。

        【讨论】:

        • DI 垂直于这个问题。 DI 正是如此——通过工厂将依赖项注入对象。它没有说明这些依赖关系的性质。这里其他答案的混淆可能是因为大多数 DI 框架都包含动态配置和服务定位器。两者都可以在没有 DI 的情况下使用,并且 DI 在技术上可以在没有它们的情况下使用。
        • 想想 DependencyManager.Register(Singleton::Instance())
        【解决方案7】:

        单例实际上是全局状态。如果你要创建很多单例,你会创建很多全局状态,但不一定看起来像全局状态。

        这使得构建单元测试、提供模拟类和重用代码等事情变得困难,因为它真的很容易 将当前状态耦合到一个函数。即函数foo只有在class Xstate Z中时才有效,否则不起作用。

        正确构建线程安全的单例也是有问题的。

        单例可以很好地协调对资源的访问,尤其是没有太多状态且构建成本高昂的资源。

        那么你为什么认为你需要很多个单例?如果您询问您的问题领域以及您遇到的问题,您可能会得到更好的答复。

        【讨论】:

          【解决方案8】:

          在编程中没有灵丹妙药。使每个类都成为单例不会神奇地使您的代码“更好”。单例是一种解决特定问题的工具,我对单例进行了更多研究。

          【讨论】:

            【解决方案9】:

            在您的项目中使用单例模式应该是一个经过深思熟虑和谨慎的设计决策,因为它是一种单向跟踪,回溯的空间很小。我已经在我的一个项目中将它实际用于多线程环境中的商业产品,并面临许多问题。但这并不意味着它是一种不可触碰的模式。关键是任何可以用单例实现的东西都可以在没有它的情况下实现,而且麻烦和复杂性更少。有关这方面的更多信息,您可以跟踪 this question 几个月前我问过。它有有趣的链接和对单例模式的洞察

            【讨论】:

              猜你喜欢
              • 2011-06-13
              • 1970-01-01
              • 2016-06-15
              • 2016-01-06
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-06-27
              相关资源
              最近更新 更多