关于设计模式:什么时候应该使用单例模式?

美化的全局变量-变成一个美化的全局类。有人说打破面向对象设计。

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

287846 次浏览

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

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

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

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

使用单例的方法之一是覆盖一个实例,其中必须有一个“代理”控制对资源的访问。单例在日志记录器中很好,因为它们代理访问,比如说,一个文件,这个文件只能被写入。对于像日志这样的东西,它们提供了一种方法来抽象出对日志文件之类的东西的写操作——你可以将缓存机制包装到你的单例中,等等……

也可以考虑这样一种情况,你有一个应用程序,有许多窗口/线程等,但它需要一个单一的通信点。我曾经使用它来控制我希望应用程序启动的作业。单例程序负责将作业序列化,并将它们的状态显示给程序中其他感兴趣的部分。在这种情况下,你可以把单例对象看作是在应用程序中运行的“服务器”类……HTH

在处理可插模块时,我将它用于封装命令行参数的对象。主程序不知道加载的模块的命令行参数是什么(甚至不总是知道正在加载的模块是什么)。如主要负荷,它本身不需要任何参数(为什么应该采取额外的指针/引用/不管,我不确定-看起来像污染),然后加载模块X, Y,和Z两个,说X和Z,需要(或接受)参数,所以他们回电话告诉它什么参数的命令行单接受,在运行时和他们回电话如果用户指定其中任何一个。

在很多方面,处理CGI参数的单例方式与你每次查询只使用一个进程类似(其他mod_*方法不这样做,所以这很糟糕——因此这个参数说你不应该在mod_cgi世界中使用单例,以防你移植到mod_perl或其他世界)。

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

正如大家所说,共享资源——特别是不能处理并发访问的资源。

我所见过的一个具体例子是Lucene搜索索引写入器。

只读单例存储一些全局状态(用户语言、帮助文件路径、应用程序路径)是合理的。使用单例控制业务逻辑时要小心——单例几乎总是以多例告终

将特定的基础设施关注点配置为单例或全局变量是非常实用的。我最喜欢的例子是依赖注入框架,它使用单例作为框架的连接点。

在这种情况下,您将依赖于基础设施来简化库的使用并避免不必要的复杂性。

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

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

如果你提议的单例只有其中的一两个需求,那么重新设计几乎总是正确的选择。

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

简单的日志记录器是可能有效的单例的最明显的例子,但这可能会在更复杂的日志记录方案中发生变化。

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

当使用单例时,你应该确保你不会意外地隐藏依赖关系。理想情况下,单例对象(就像应用程序中的大多数静态变量一样)在应用程序的初始化代码执行期间设置(c#可执行文件为静态void Main(), java可执行文件为静态void Main()),然后传递给所有其他需要它的实例化类。这有助于维护可测试性。

在我寻求真理的过程中,我发现实际上很少有“可以接受的”;使用单例的原因。

在互联网上反复出现的一个原因是“日志”;类(你提到的)。在这种情况下,可以使用Singleton来代替类的单个实例,因为日志类通常需要被项目中的每个类反复使用,令人作呕。如果每个类都使用这个日志类,依赖注入就变得很麻烦。

日志记录是“可接受的”的一个具体例子。因为它不会影响代码的执行。禁用日志记录,代码执行保持不变。启用它,一样。Misko在单身的根本原因中这样说:“这里的信息单向流动:从应用程序流向记录器。即使记录器是全局状态,由于没有信息从记录器流入应用程序,记录器也是可以接受的。”

我相信还有其他合理的原因。Alex Miller在“我讨厌的图案"中谈到服务定位器和客户端UI也可能是“可接受的”。选择。

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

也许是一个带有代码的示例。

在这里,ConcreteRegistry是一个纸牌游戏中的单例,它允许行为沿着包树向上访问游戏的少数核心接口(即模型、视图、控制器、环境等的外观):

http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html

艾德。

测试:建造者中可以找到单例的一个实际示例,这个类支持几乎所有现代Perl测试模块。Builder单例存储并代理测试流程的状态和历史记录(历史测试结果,计算运行的测试次数)以及测试输出流向等内容。这些都是协调由不同作者编写的多个测试模块以在单个测试脚本中一起工作所必需的。

Test::Builder的单例的历史是有教育意义的。调用new()总是给您相同的对象。首先,所有数据都存储为类变量,对象本身没有任何内容。这一直工作到我想测试test::Builder本身。然后我需要两个Test::Builder对象,一个设置为虚拟对象,用于捕获和测试它的行为和输出,另一个是真正的测试对象。此时,Test::Builder被重构为一个真实的对象。单例对象被存储为类数据,new()总是会返回它。添加create()是为了创建一个新对象并启用测试。

目前,用户希望在他们自己的模块中更改Test::Builder的一些行为,而不影响其他模块,而所有测试模块的测试历史记录保持一致。现在发生的事情是整体Test::Builder对象被分解成更小的部分(历史记录、输出、格式……),并由Test::Builder实例将它们收集在一起。现在Test::Builder不再是单例。它的组成部分,如历史,可以是。这就把单例的不可改变的必要性降低了一个层次。它为用户混合和匹配部件提供了更大的灵活性。较小的单例对象现在可以只存储数据,由它们的包含对象决定如何使用数据。它甚至允许非Test::Builder类使用Test::Builder历史记录和输出单例。

似乎在数据的协调和行为的灵活性之间存在着一种推拉关系,这种关系可以通过在共享数据周围使用尽可能少的行为来缓解,以确保数据的完整性。

当你加载一个配置属性对象时,无论是从数据库还是从文件中,将它作为一个单例都会有所帮助;没有理由在服务器运行时重新读取不会改变的静态数据。

您可以在实现状态模式时使用单例(以GoF书中所示的方式)。这是因为具体的State类没有自己的状态,而是根据上下文类执行它们的操作。

你也可以让抽象工厂成为一个单例。

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

这是一个真正的对象使用——>模板类包含正在构建的页面模板,它被添加到页面输出的模块塑造、添加、更改。它必须保持为单个实例,这样才能实现这一点,数据库也是如此。使用共享数据库单例,所有模块的类都可以访问查询,并且无需重新运行查询。

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

# EYZ0

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

# EYZ0

在运行页面代码的最后,你会得到,比如说,一个现在成熟的:

# EYZ0

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

这样做的最大好处是你可以对任何东西做任何你喜欢的后期处理。您可以将所有语言值输送到谷歌translate或其他翻译服务,并将它们返回,并将它们替换到它们的位置,例如,已翻译。或者,您可以根据需要替换页面结构或内容字符串。

我认为单例的使用与数据库中的多对一关系是一样的。如果代码中有许多不同的部分需要处理对象的单个实例,那么使用单例就很有意义了。

首先,让我们区分单一对象单例。后者是前者的众多之一可能实现。而且单对象的问题与单例的问题是不同的。单对象本身并不坏,有时是做事情的唯一方法。简而言之:

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


private Singleton() {}


public static Singleton instance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

如你所见,“单身”;规范形式的模式不太适合测试。不过,这个问题很容易解决:只要让Singleton实现一个接口。让我们称之为“可测试单例”;:)

public class Singleton implements ISingleton {
private static Singleton instance;


private Singleton() {}


public static ISingleton instance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

现在我们可以模拟Singleton了,因为我们通过接口来使用它。其中一项索赔消失了。让我们看看是否可以去掉另一个声明-共享全局状态。

如果我们剥离单例模式,它的核心是惰性初始化:

public static ISingleton instance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

这就是它存在的全部原因。# EYZ0。我们把它拿走,放到工厂方法中,例如:

public class SingletonFactory {
private static ISingleton instance;


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

与我们的可测试单例有什么不同?这里有没有一个,因为这是单对象模式的精髓——不管你是把它实现为单例、工厂方法还是服务定位器。你仍然有一些共享的全局状态。如果从多个线程访问它,这可能会成为一个问题。您必须使simpleSingleton()同步并处理所有多线程问题。

再说一遍:无论你选择什么方法,你都必须付出单一对象的代价。使用依赖注入容器只是将复杂性转移到框架,它将不得不处理单对象的固有问题。

回顾:

  1. 大多数提到Singleton的人指的是单一对象
  2. 一种流行的实现方法是单例模式
  3. 它有可以缓解的缺陷
  4. 然而,单例的复杂性主要来源于单对象的复杂性
  5. 不管你如何实例化你的单对象,它仍然存在,它是一个服务定位器,一个工厂方法或其他东西
  6. 您可以将复杂性转移到(希望)经过良好测试的DI容器
  7. 有时使用DI容器很麻烦——想象一下向每个类注入一个LOGGER

我认为如果你的应用程序有多个层次,如表示,领域和模型。Singleton是横切层的一个很好的候选者。并为系统各层提供服务。

从本质上讲,单例包装了一个服务,例如日志记录、分析,并将其提供给系统中的其他层。

是的,单例需要遵循单一责任原则。

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

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

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

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

因此,我正在为学校阅读单例模式,教授们策划了一份关于该主题的当前观点和最佳实践的列表。似乎有一个共识,即如果构建时不向代码中添加任何内容,则可以使用单例。如果您使单例使用可以被开启和关闭,并且除了工作负载之外没有其他副作用,那么使用这种设计模式是安全的,也是可取的。

单例模式是Spring容器化方法中最普遍的模式。如果我们从架构原语的角度来看,它们形成了一个对象的黑板图,每个线程都可以对其进行读写。它们在多个线程之间执行戏剧性的同步操作。多个线程需要同步的原因是,计算程序的基础上总是存在资源,可能会发生争用。考虑一下所谓的“最后一个座位问题”。机票已经订好了,但是有多种方法可以做到。为简单起见,我们假设关于航班占用率的数据存储在平面文件中,而不是数据库中。现在,如果有两个线程,每个线程的功能不同(即在webapp中由不同的端点表示),并让其中一个线程A是潜在乘客用来进行预订的线程,另一个线程B是航班经理用来关闭预订的线程——实际上关闭了登机门。然后,如果这些线程不使用单例,那么flight对象将从实际资源中分离出来,我们说的不是实际的飞机,而是平面文件中的条目。A线程指向一个对象,而乘客还在纠结是否要飞,最后当他下定决心的时候,B线程已经关上了门。但是A线程引用的对象仍然显示还有一个座位。现在,由于我们最初的假设,删除了RDBMS,系统会为乘客写一张票,并将它发给他,即使登机是关闭的。现在,在单例实现中,当读B访问系统时,通用对象Flight以关闭状态更新。因此,如果乘客最终下定决心并点击确认,他将立即得到一个错误。如果没有单例,这一切都不可能实现。因此,单例可以让您保持接近资源并避免线程争用。

我不认为Singleton的场景与记录器、打印机池或任何示例相关。

单例决策的目的是优化硬件资源,而不是只有一个地方来控制任何记录器或打印机池

我个人认为单例应该在以下情况下使用:

  1. 我们正在谈论的对象总是以相同的方式实例化(即任何共享资源,如Logger或打印机池)

  2. 它被多次调用(这可以是100或1000,它与您的资源有关)

  3. 你的硬件资源是有限的(例如内存,处理能力等)。

如果你有大量的内存空间和处理能力,我认为没有必要使用单例。

Singleton将确保你只有一个实例,并且是惰性加载的,那么如果它被调用一百万次,你就只创建了一个对象。

Singleton类的主要目的是限制创建的实例数量,从而确保对资源的访问控制。

使用单例类不会产生内存空间浪费,因为它限制了实例的创建。因为对象的创建只会发生一次,而不是每次新请求时都创建。

Singleton类在内部使用互斥量,因此使其线程安全。这就是为什么多线程和数据库应用程序主要使用Java中的Singleton模式进行缓存、日志记录、线程池、配置设置等等