关于设计模式:我什么时候应该使用单身人士?

Set*_*ori 421 singleton design-patterns

荣耀的全球变量 - 成为一个荣耀的全球阶级.有人说打破面向对象的设计.

给我一些场景,除了好的旧记录器,使用单例是有意义的.

Cod*_*nts 347

在我追求真相的过程中,我发现使用Singleton实际上很少有"可接受"的理由.

在互联网上一次又一次地出现的一个原因是"日志记录"类(你提到过).在这种情况下,可以使用Singleton而不是类的单个实例,因为项目中的每个类通常需要反复使用日志记录类.如果每个类都使用此日志记录类,则依赖项注入会变得很麻烦.

日志记录是"可接受的"Singleton的一个特定示例,因为它不会影响代码的执行.禁用日志记录,代码执行保持不变.启用它,同样相同.Misko在单根的根本原因中将其置于以下方式,"这里的信息以一种方式流动:从您的应用程序进入记录器.即使记录器是全局状态,由于没有信息从记录器流入您的应用程序,因此记录器是可接受的."

我相信还有其他正当理由.Alex Miller在" 模式我讨厌 "中讨论了服务定位器和客户端UI也可能是"可接受的"选择.

在Singleton阅读更多我爱你,但你让我失望.

  • 我知道这是一个老问题,这个答案中的信息很棒。但是,当 OP 明确指定时,我无法理解为什么这是公认的答案:“给我一些场景,而不是使用单例有意义的旧记录器。” (5认同)
  • @ArneMertz我猜[this](http://geekswithblogs.net/AngelEyes/archive/2013/09/08/singleton-i-love-you-but-youre-bringing-me-down-re-uploaded.aspx)是这个. (3认同)
  • 当您需要管理资源时,单例是最好的。例如,Http 连接。您不想为单个客户端建立 100 万个 http 客户端,这非常浪费且缓慢。因此,具有连接池 http 客户端的单例将会更快且资源友好。 (3认同)
  • 为什么不能只使用全局对象?为什么它必须是单身人士? (2认同)

met*_*tao 120

Singleton候选人必须满足三个要求:

  • 控制对共享资源的并发访问.
  • 将从系统的多个不同部分请求访问资源.
  • 只能有一个对象.

如果您提出的Singleton只有一个或两个这样的要求,重新设计几乎总是正确的选择.

例如,不太可能从多个位置("打印"菜单)调用打印机假脱机程序,因此您可以使用互斥锁来解决并发访问问题.

简单的记录器是可能有效的Singleton的最明显的例子,但是这可以随着更复杂的记录方案而改变.

  • 我想你错过了"候选人"这个词.Singleton候选人必须满足这三个要求; 只是因为某些东西符合要求,并不意味着它应该是一个单身人士.可能还有其他设计因素:) (13认同)
  • 我不同意第2点.第3点并不是一个真正的理由(仅仅因为你不能表示你应该这样做)而且1是一个好点,但我仍然没有看到它的使用.可以说共享资源是磁盘驱动器或数据库缓存.您可以添加另一个驱动器或让数据库缓存专注于另一个事物(例如,一个线程的专用表的缓存,而另一个更通用). (3认同)

Pau*_*kin 43

读取应该只在启动时读取并将它们封装在Singleton中的配置文件.

  • 类似于.NET中的`Properties.Settings.Default`. (8认同)
  • @Paul,"no-singleton camp"将声明配置对象应该简单地传递给需要它的函数,而不是让它可以全局访问(也就是单例). (8认同)
  • @PaulCroarkin你能否扩展一下并解释这有什么好处? (3认同)
  • 不同意.如果将配置移动到数据库,一切都搞砸了.如果配置的路径依赖于该单例之外的任何东西,那么这些东西也需要是静态的. (2认同)
  • @rr-如果配置移动到数据库,它仍然可以封装在配置对象中,该对象将被传递到需要它的函数中。(PS,我不属于“非单身”阵营)。 (2认同)

Vin*_*nie 35

您需要管理共享资源时使用单例.例如打印机假脱机程序.您的应用程序应该只有一个假脱机程序实例,以避免对同一资源的请求冲突.

或者数据库连接或文件管理器等.

  • 我听过这个打印机假脱机示例,我认为这有点蹩脚.谁说我不能有多个假脱机?什么是打印机假脱机呢?如果我有不同类型的打印机不能冲突或使用不同的驱动程序怎么办? (30认同)
  • 它只是一个例子......对于任何人用作例子的任何情况,你将能够找到一个替代设计,使这个例子无用.让我们假设假脱机程序管理由多个组件共享的单个资源.有用. (5认同)
  • 这是四人帮的经典例子。我认为带有**真实**试用用例的答案会更有用。我的意思是在这种情况下,您实际上认为 Singleton 是最佳解决方案。 (3认同)
  • 打印机假脱机程序到底是什么? (2认同)

Mar*_*ett 23

只读单例存储一些全局状态(用户语言,帮助文件路径,应用程序路径)是合理的.小心使用单例来控制业务逻辑 - 单个几乎总是最终成为多个

  • 用户语言只能是单例,假设只有一个用户可以使用该系统. (4认同)
  • @SamuelÅslund 如果它是一个桌面应用程序,这是一个合理的假设 (4认同)
  • ......并且一个用户只会说一种语言。 (3认同)

Fed*_*oni 16

管理数据库的连接(或连接池).

我还会用它来检索和存储外部配置文件的信息.

  • @Ken你几乎在所有情况下都希望这家工厂成为单身人士. (3认同)
  • 你真的不需要一个单身人士.它可以注射. (3认同)
  • 数据库连接生成器不是工厂的示例吗? (2认同)
  • @Federico,"no-singleton camp"将声明这些数据库连接应该简单地传递给需要它们的函数,而不是让它们全局可访问(也就是单例). (2认同)

Dav*_*kle 10

使用单例的一种方法是覆盖必须有一个"代理"控制对资源的访问的实例.单身人士在记录器方面表现优异,因为他们可以代理访问一个文件,这个文件只能写入.对于像日志记录这样的东西,它们提供了一种将写入抽象为日志文件的方法 - 你可以将缓存机制包装到你的单例等等......

还要考虑一种情况,你有一个应用程序有许多windows/threads/etc,但需要单点通信.我曾经用一个来控制我希望我的应用程序启动的作业.单身人员负责序列化工作并将其状态显示给感兴趣的程序的任何其他部分.在这种情况下,你可以看一个单例就像在你的应用程序中运行的"服务器"类...... HTH

  • 记录器通常是单件,因此不必传递记录对象.任何适当的日志流实现都将确保并发写入是不可能的,无论它是否是Singleton. (3认同)

Ada*_*ess 10

在管理对整个应用程序共享的资源的访问时,应使用单例,并且可能具有同一类的多个实例是破坏性的.确保对共享资源线程安全的访问是这种模式至关重要的一个非常好的例子.

使用单身人士时,你应该确保你不会意外地隐藏依赖关系.理想情况下,单个应用程序(如应用程序中的大多数静态变量)在执行应用程序的初始化代码期间设置(静态void Main()用于C#可执行文件,static void main()用于java可执行文件)然后传入所有其他需要实例化的类.这有助于您保持可测试性.


daa*_*ert 7

我认为单例使用可以认为与数据库中的多对一关系相同。如果您的代码中有许多不同的部分需要使用一个对象的单个实例,那么使用单例才有意义。


Sch*_*ern 6

单例的一个实际例子可以在Test :: Builder中找到,这个类几乎支持每个现代Perl测试模块.Test :: Builder singleton存储和代理测试过程的状态和历史(历史测试结果,计算运行测试的数量)以及测试输出的去向.这些都是协调由不同作者编写的多个测试模块在一个测试脚本中协同工作所必需的.

Test :: Builder的单身人士的历史是教育性的.调用new()总是给你相同的对象.首先,所有数据都存储为类变量,对象本身没有任何内容.这一直有效,直到我想用自己测试Test :: Builder.然后我需要两个Test :: Builder对象,一个设置为虚拟对象,捕获并测试其行为和输出,一个是真正的测试对象.此时,Test :: Builder被重构为一个真实的对象.单例对象存储为类数据,并new()始终返回它. create()添加了一个新对象并启用测试.

目前,用户希望在自己的模块中更改Test :: Builder的某些行为,但是让其他人保持独立,而测试历史在所有测试模块中保持共同.现在发生的事情是单片Test :: Builder对象被分解成更小的片段(历史,输出,格式......),其中一个Test :: Builder实例将它们收集在一起.现在,Test :: Builder不再是一个单身人士.它的组成部分,如历史,可以.这将单身人士的灵活必要性推向了一个层次.它为用户提供了更多的混合和匹配件的灵活性.较小的单例对象现在可以只存储数据,其包含的对象决定如何使用它.它甚至允许非Test :: Builder类通过使用Test :: Builder历史记录和输出单例来进行.

似乎是在数据协调和行为灵活性之间存在推动和拉动,这可以通过将单例放在仅具有最小行为的共享数据来减轻,以确保数据完整性.


Dea*_*n J 5

当您从数据库或文件中加载配置属性对象时,将其作为单例是有帮助的。没有理由继续读取服务器运行时不会改变的静态数据。

  • 为什么不只加载一次数据并根据需要传递配置对象? (2认同)

小智 5

共享资源。特别是在PHP中,数据库类、模板类和全局变量库类。所有这些都必须由整个代码中使用的所有模块/类共享。

这是真正的对象用法 -> 模板类包含正在构建的页面模板,并且它由添加到页面输出的模块来塑造、添加和更改。它必须保留为单个实例才能发生这种情况,数据库也是如此。通过共享数据库单例,所有模块的类都可以访问查询并获取它们,而无需重新运行它们。

全局变量库单例为您提供了一个全局、可靠且易于使用的变量库。它极大地整理了你的代码。想象一下,将所有配置值放在一个单例数组中,如下所示:

$gb->config['hostname']

或者将所有语言值放在一个数组中,例如:

$gb->lang['ENTER_USER']

在运行页面代码结束时,您会得到一个现在成熟的:

$template

Singleton,一个$gb具有用于替换的 lang 数组的单例,并且所有输出均已加载并准备就绪。您只需将它们替换为现在存在于成熟模板对象的页面值中的键,然后将其提供给用户。

这样做的最大优点是您可以对任何东西进行任何您喜欢的后期处理。您可以将所有语言值通过管道传输到谷歌翻译或其他翻译服务并将它们取回,然后将它们替换到翻译后的位置。或者,您可以根据需要替换页面结构或内容字符串。

  • 您可能希望将答案分成多个段落并屏蔽代码段以提高可读性。 (21认同)

rai*_*iks 5

首先,让我们区分Single ObjectSingleton。后者是前者的许多可能实现方式之一。而且 Single Object 的问题与 Singleton 的问题不同。单个对象本质上并不是坏事,有时是做事的唯一方法。简而言之:

  • 单个对象 - 我只需要程序中对象的一个​​实例
  • Singleton - 创建一个带有静态字段的类。添加返回该字段的静态方法。在第一次调用时延迟实例化字段。始终返回相同的对象。
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton instance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,规范形式的“单例”模式不太适合测试。不过,这很容易解决:只需让 Singleton 实现一个接口即可。我们称之为“可测试单例”:)

public class Singleton implements ISingleton {
    private static Singleton instance;

    private Singleton() {}

    public static ISingleton instance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以模拟 Singleton,因为我们通过接口使用它。其中一项索赔已经消失。让我们看看我们是否可以摆脱另一个主张——共享全局状态。

如果我们剥离单例模式,其核心就是延迟初始化:

public static ISingleton instance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}
Run Code Online (Sandbox Code Playgroud)

这就是它存在的全部理由。这就是单一对象模式。我们把它拿走并放入工厂方法中,例如:

public class SingletonFactory {
    private static ISingleton instance;

    // Knock-knock. Single Object here
    public static ISingleton simpleSingleton() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

和我们的Testable Singleton有什么区别?没有因为这是单一对象模式的本质 - 无论您将其实现为单例、工厂方法还是服务定位器,都没有关系。您仍然拥有一些共享的全局状态。如果从多个线程访问它,这可能会成为一个问题。你必须做simpleSingleton()同步并处理所有多线程问题。

再一次:无论您选择哪种方法,您都必须支付单个对象的价格。使用依赖注入容器只是将复杂性转移到框架上,框架必须处理单个对象的固有问题。

回顾:

  1. 大多数人提到 Singleton 的意思是 Single Object
  2. 实现它的流行方法之一是单例模式
  3. 它有可以弥补的缺陷
  4. 然而,Singleton 的复杂性大部分源于 Single Object 的复杂性
  5. 无论您如何实例化单个对象,它仍然存在,无论是服务定位器、工厂方法还是其他东西
  6. 您可以将复杂性转移到(希望)经过充分测试的 DI 容器
  7. 有时使用 DI 容器很麻烦 - 想象一下向每个类注入一个 LOGGER


Lak*_*sam 5

当您想要确保一个类有一个实例,并且该实例将具有对其的全局访问点时,可以使用单例设计模式。

假设您有一个应用程序需要数据库来处理 CRUD 操作。理想情况下,您可以使用与数据库相同的连接对象来访问数据库并执行 CRUD 操作。

因此,为了确保数据库类拥有一个对象,并且在整个应用程序中使用同一个对象,我们实现了单例设计模式。

确保您的构造函数是私有的,并且提供静态方法来提供对单例类的单个对象的访问