代码之家  ›  专栏  ›  技术社区  ›  Adam

好的设计还是拐杖?[关闭]

  •  66
  • Adam  · 技术社区  · 16 年前

    singleton是一个备受争议的设计模式,所以我对stack overflow社区对它们的看法很感兴趣。

    请为您的意见提供理由,而不仅仅是“单例是为懒惰的程序员!”

    关于这个问题,这里有一篇相当好的文章,尽管它反对使用单例: scientificninja.com: performant-singletons .

    有没有其他好文章?也许是为了支持单身汉?

    23 回复  |  直到 5 年前
        1
  •  56
  •   Peter Mortensen icecrime    15 年前

    为了保护单身汉:

    • 它们没有地球人那么坏 因为globals没有标准的强制初始化顺序,而且您很容易看到由于原始的或意外的依赖顺序而导致的不确定错误。singleton(假设它们是在堆上分配的)是在所有全局变量之后创建的,并且位于代码中非常可预测的位置。
    • 它们对于资源延迟/缓存系统非常有用 例如一个接口到一个缓慢的I/O设备。如果你智能地为一个速度慢的设备构建一个单一的接口,并且没有人调用它,你就不会浪费任何时间。如果另一段代码从多个地方调用它,那么您的singleton可以同时优化这两个位置的缓存,并避免任何双重查找。您还可以轻松避免在单例控制的资源上出现任何死锁情况。

    反对单身:

    • 在C++中,没有一种很好的方法可以在单体之后自动清理。 有解决办法,也有一些简单的方法,但是没有简单的通用方法来确保你的singleton的析构函数总是被调用。这并不是一个糟糕的记忆方面的问题——只要把它看作是更多的全局变量就可以了。但是,如果您的singleton分配了其他资源(例如,锁定一些文件)而不释放它们,这可能是不好的。

    我个人的看法:

    我用单子,但如果有合理的选择就不要用。到目前为止,这对我来说很有效,我发现它们是可测试的,尽管需要测试的工作稍微多一些。

        2
  •  37
  •   Brian    16 年前

    谷歌有 Singleton Detector 对于java,我相信它最初是一个必须在google生成的所有代码上运行的工具。删除单重态的简单原因:

    因为他们可以做测试 困难和隐藏的问题 设计

    有关更明确的解释,请参见 Why Singletons Are Controversial 来自谷歌。

        3
  •  31
  •   Orion Edwards    16 年前

    单身只是一件奇装异服中的一堆全局变量。

    全局变量有它们的用途,就像单变量一样,但是如果你认为你用单变量做了一些很酷很有用的事情,而不是用一个讨厌的全局变量(大家都知道全局变量是坏的mmkay),你很不幸被误导了。

        4
  •  26
  •   JacquesB    16 年前

    单例的目的是确保一个类只有一个实例,并提供对它的全局访问点。大多数情况下,重点是单个实例点。想象一下,如果它被称为一个地球仪。这听起来不那么吸引人,因为这强调了(通常)全局变量的负面含义。

    反对单例的大多数好论点都与它们在测试中的困难有关,因为为它们创建测试双例并不容易。

        5
  •  22
  •   Bleadof    16 年前
        6
  •  19
  •   Chap    16 年前

    独生子女是 一个可怕的模式,尽管它是 误用 很多。我认为这种误用是因为它是一种更简单的模式,而且对于singleton来说,最新的模式都被全球副作用所吸引。

    Erich Gamma 他说单身汉是一个他希望不包括在gof的书,这是一个糟糕的设计模式。我倾向于不同意。

    如果使用模式是为了在任何给定时间创建对象的单个实例,则模式将被正确使用。如果使用singleton是为了产生全局效果,那么它的使用是不正确的。

    缺点:

    • 在调用singleton的整个代码中,您都耦合到一个类
      • 创建单元测试的麻烦,因为很难用模拟对象替换实例
      • 如果由于需要多个实例而需要稍后对代码进行重构,则比将singleton类传递到使用它的对象(使用接口)更痛苦

    优势:

    • 类的一个实例在任意给定的时间点上表示。
      • 通过设计,你正在执行这个
    • 实例在需要时创建
    • 全局访问是一个副作用
        7
  •  16
  •   Peter Mortensen icecrime    15 年前

    小妞们喜欢我,因为我很少用单打,当我用的时候,这是很不寻常的。不,说真的,我喜欢单身模式。你知道为什么吗?因为:

    1. 我懒惰。
    2. 什么都不会出错。

    当然,“专家”们会到处谈论“单元测试”和“依赖注入”,但这都是野狗的肾脏负担。你说单子很难单元测试?没问题!只要把所有的事情都公开,把你的班级变成一个充满全球美好事物的有趣之家。你还记得20世纪90年代的《高地人》吗?单身汉就是这样,因为:A。它永远不会死;B。只有一个。所以别再听那些废话了,尽情地实现你的单身。以下是一些更合理的理由…

    • 每个人都在做。
    • 单重模式让你无敌。
    • 单曲押韵“赢”(或“乐趣”取决于你的口音)。
        8
  •  8
  •   Cem Catikkas    16 年前

    我认为对单例模式的使用存在很大的误解。这里的大多数评论都将其称为访问全局数据的地方。我们得小心点-单例 用于访问全局。

    辛格尔顿应该被用来 只有一个实例 属于给定的类。 Pattern Repository 有很多关于 Singleton .

        9
  •  6
  •   andreas buykx    16 年前

    与我共事过的一位同事非常专一。每当有什么东西是经理或老板喜欢的对象,他会把它变成一个单一的,因为他认为应该只有一个老板。每次系统提出一些新的要求,都有充分的理由允许多个实例。

    我想说,如果域模型指示(而不是“建议”)有一个,那么应该使用singleton。所有其他情况都只是一个类的独立实例。

        10
  •  5
  •   Dan Blair    16 年前

    我一直在想办法来拯救可怜的辛格顿,但我必须承认这很难。我很少看到它们的合法使用,而且使用当前的驱动器进行依赖注入和单元测试,它们很难使用。它们无疑是用设计模式编程的“货物崇拜”表现,我曾与许多程序员合作过,他们从未破解过“gof”一书,但他们知道“singleton”,因此他们知道“模式”。

    不过,我确实不同意猎户座的观点,大多数时候,我都看到过singleton的过度使用,这不是裙子中的全局变量,而是裙子中的全局服务(方法)。有趣的是,如果您尝试在sql server 2005中通过clr接口以安全模式使用singleton,系统将标记代码。问题是,除了可能运行的任何给定事务之外,还有持久数据,当然,如果将实例变量设置为只读,则可以解决此问题。

    这个问题导致了我一年的大量返工。

        11
  •  5
  •   Gishu    16 年前

    圣战!好的,让我看看。上次我检查设计时警察说…

    单例是不好的,因为它们阻碍了自动测试——不能为每个测试用例重新创建实例。 相反,逻辑应该在一个类(a)中,该类可以很容易地实例化和测试。另一个类(b)应该负责约束创建。突出单一责任原则!这应该是团队知识,你应该通过B访问A类的团队会议。

    我基本同意。

        12
  •  4
  •   Esko Luontola    16 年前

    许多应用程序要求某个类只有一个实例,因此只有一个类实例的模式非常有用。但是模式是有变化的 实施 .

    静态单重态 其中,类强制每个进程只能有一个类的实例(在Java中实际上每个类加载器都有一个实例)。另一个选择是 只创建一个实例 .

    静态单重态是 邪恶的 -一种全局变量。它们使测试更加困难,因为不可能完全隔离地执行测试。在每次测试之间,您需要复杂的设置和拆卸代码来清理系统,而且很容易忘记正确地清理一些全局状态,这反过来又可能导致测试中出现未指定的行为。

    只创建一个实例 好的 . 程序启动时只需创建一个实例,然后将指向该实例的指针传递给需要它的所有其他对象。依赖注入框架使这一点变得简单-您只需配置对象的范围,di框架将负责创建实例并将其传递给所有需要它的人。例如,在guice中,您将使用@singleton注释类,di框架将只创建类的一个实例(每个应用程序-您可以在同一个jvm中运行多个应用程序)。这使得测试变得容易,因为您可以为每个测试创建一个类的新实例,并让垃圾收集器在不再使用该实例时销毁该实例。没有全局状态会从一个测试泄漏到另一个测试。

    有关详细信息: The Clean Code Talks - "Global State and Singletons"

        13
  •  3
  •   MSN    16 年前

    单例作为实现细节是可以的。单例作为接口或访问机制是一个巨大的pita。

    不接受返回对象实例的参数的静态方法与仅使用全局变量略有不同。相反,如果一个对象通过构造函数或其他方法传递了对singleton对象的引用,那么singleton的实际创建方式和整个模式都无关紧要。

        14
  •  2
  •   Dan Blair    16 年前

    这不仅仅是一件奇装异服中的一堆变量,因为这有很多职责,比如与持久层通信以保存/检索公司数据、处理员工和价格收集等。

    我必须说,你并没有真正描述应该是单个对象的somthing,除了数据序列化之外,它们中的任何一个都应该是singleton,这是值得商榷的。

    我可以看到至少3组我通常会在其中设计的类,但我倾向于使用更小更简单的对象,这些对象可以很好地完成狭窄的任务集。我知道这不是大多数程序员的天性。(是的,我每天处理5000行类怪物,我特别喜欢一些人写的1200行方法。)

    我认为关键是,在大多数情况下,你不需要一个歌手,往往你只是让你的生活更困难。

        15
  •  2
  •   Bill Michell    16 年前

    单例的最大问题是它们进行单元测试 ,尤其是当您希望并行但独立地运行测试时。

    第二个原因是人们经常认为使用双重检查锁定的惰性初始化是实现它们的好方法。

    最后,除非您的单例是不可变的,否则当您尝试将应用程序扩展到在多个处理器上的多个线程中运行时,它们很容易成为性能问题。竞争同步在大多数环境中都很昂贵。

        16
  •  1
  •   Jon Limjap    16 年前

    单例有它们的用途,但是在使用和公开它们时必须小心,因为它们是 way too easy to abuse ,很难真正进行单元测试,而且很容易基于两个相互访问的单例创建循环依赖项。

    不过,这很有帮助,因为当您希望确保所有数据跨多个实例同步时,例如,分布式应用程序的配置可能依赖于单个实例来确保所有连接使用相同的最新数据集。

        17
  •  1
  •   Dan Herbert    16 年前

    我发现你必须非常小心 为什么 你决定用一个单子。正如其他人提到的,这与使用全局变量本质上是同一个问题。你必须非常谨慎,并考虑你可以做什么使用一个。

    使用它们是非常罕见的,而且通常有更好的方法来做事情。我遇到过这样的情况:我用一个单例程序做了一些事情,然后在发现它使事情变得多么糟糕之后(或者在我想出了一个更好、更理智的解决方案之后),不得不仔细检查我的代码以将其删除。

        18
  •  1
  •   Vinnie    16 年前

    我用过很多次单子和 Spring 也不认为这是拐杖或懒惰。

    这个模式允许我为一堆配置类型值创建一个类,然后在我的web应用程序的几个用户之间共享该特定配置实例的单个(不可变)实例。

    在我的例子中,singleton包含特定于该客户机的客户机配置标准(css文件位置、db连接标准、功能集等)。这些类通过spring进行实例化和访问,并由具有相同配置的用户(即来自同一公司的2个用户)共享。***我知道这类应用程序有一个名称,但我不知道*

    我觉得为应用程序的每个用户创建(然后垃圾收集)这些“常量”对象的新实例是浪费。

        19
  •  1
  •   Peter Mortensen icecrime    15 年前

    我读了很多关于“singleton”,它的问题,何时使用它,等等,这些都是我到目前为止的结论:

    • singleton的经典实现和真正的需求之间的混淆:只有一个类实例!

    • 这通常是不好的实现。如果您想要一个唯一的实例,不要使用(反)模式使用静态getInstance()方法返回静态对象。这使得类负责实例化自身的单个实例并执行逻辑。这打破了 Single Responsibility Principle . 相反,这应该由工厂类实现,该类负责确保只存在一个实例。

    • 它在构造函数中使用,因为它易于使用并且不能作为参数传递。这应该通过使用 dependency injection ,这是实现良好的可测试对象模型的一个很好的模式。

    • 不是 TDD . 如果执行tdd,则会从实现中提取依赖项,因为您希望测试易于编写。这会使对象模型更好。如果使用TDD,则不会编写静态getInstance=)。顺便说一句,如果你认为对象有明确的职责,而不是类,你会得到同样的效果=)。

        20
  •  0
  •   Mario Marinato    16 年前

    我真的不同意 化装舞会上的一堆全局变量 想法。单例在解决正确的问题时非常有用。让我给你举个真正的例子。

    有一次我在一个工作的地方开发了一个小软件,有些表格需要用到一些关于公司、员工、服务和价格的信息。在第一个版本中,每次打开表单时,系统都会从数据库中加载这些数据。当然,我很快就意识到这种方法不是最好的。

    然后我创建了一个名为 公司 ,它封装了这个地方的所有内容,在系统打开时,它已经完全充满了数据。

    这不仅仅是一件奇装异服中的一堆变量,因为这有很多职责,比如与持久层通信以保存/检索公司数据、处理员工和价格收集等。

    另外,它是一个固定的、全系统的、易于访问的点,可以用来获取公司数据。

        21
  •  0
  •   Nate Kohari    16 年前

    单例非常有用,使用它们本身并不是一种反模式。然而,它们之所以名声不好,很大程度上是因为它们强迫任何消费代码承认它们是单例的,以便与它们交互。这意味着,如果你需要“非单一化”它们,对你的代码库的影响会非常显著。

    相反,我建议要么把单身汉藏在工厂后面。这样,如果将来需要更改服务的实例化行为,则只需更改工厂,而不是使用单例的所有类型。

    更好的是,使用控制容器的反转!其中大多数允许您将实例化行为与类的实现分离。

        22
  •  0
  •   Asgeir S. Nilsen    16 年前

    例如JavaSon中的一件可怕的事情就是你 可以 在某些情况下会出现同一个单例的多个实例。jvm基于 元素:类的完全限定名,以及负责加载它的类加载器。

    这意味着同一个类可以由两个不知道彼此的类加载器加载,并且应用程序的不同部分将具有与其交互的这个单例的不同实例。

        23
  •  0
  •   Ben Hardy    16 年前

    编写正常的、可测试的、可注入的对象,并让guice/spring/whatever处理实例化。说真的。

    这甚至适用于缓存或任何单例的自然用例。 没有必要重复编写代码的可怕之处来尝试强制执行一个实例。 让依赖注入框架来处理它。(如果还没有使用轻量级di容器,我建议使用guice)。