荣耀的全局变量-成为荣耀的全局类。有人说打破了面向对象的设计。
给我一些方案,除了一个好的老记录器之外,使用单例是有意义的。
荣耀的全局变量-成为荣耀的全局类。有人说打破了面向对象的设计。
给我一些方案,除了一个好的老记录器之外,使用单例是有意义的。
Answers:
在寻求真相的过程中,我发现实际上很少有使用“单例”的“可接受”理由。
互联网上反复出现的一个原因是“记录”类的原因(您提到过)。在这种情况下,可以使用Singleton代替类的单个实例,因为项目中的每个类通常需要一遍又一遍地重复使用日志记录类。如果每个类都使用此日志记录类,则依赖项注入将变得很麻烦。
日志记录是“可接受的”单例的特定示例,因为它不会影响代码的执行。禁用日志记录,代码执行保持不变。启用它,相同。Misko在“ 单身人士的根本原因 ” 中以以下方式表示:“信息以一种方式流动:从您的应用程序进入记录器。即使记录器处于全局状态,由于没有信息从记录器流入您的应用程序,因此记录器是可以接受的。”
我敢肯定还有其他正当理由。Alex Miller在“ 我讨厌的模式 ”一文中谈到了服务定位器和客户端UI也可能是“可接受的”选择。
单身人士候选人必须满足三个要求:
如果您提出的Singleton仅具有这些要求中的一个或两个,那么重新设计几乎总是正确的选择。
例如,一个打印机后台处理程序不太可能从多个位置调用(“打印”菜单),因此您可以使用互斥锁解决并发访问问题。
一个简单的记录器是一个可能有效的Singleton的最明显的例子,但是随着更复杂的记录方案的改变,这种情况可能会改变。
读取仅应在启动时读取的配置文件,并将其封装在Singleton中。
Properties.Settings.Default
.NET。
当您需要管理共享资源时,可以使用单例。例如打印机后台处理程序。您的应用程序应仅具有一个后台处理程序实例,以避免对相同资源的请求冲突。
或数据库连接或文件管理器等。
只读的单例存储一些全局状态(用户语言,帮助文件路径,应用程序路径)是合理的。小心使用单例来控制业务逻辑-单个几乎总是以多个结尾
管理与数据库的连接(或连接池)。
我还将使用它来检索和存储有关外部配置文件的信息。
使用单例的一种方法是覆盖必须有一个“代理”来控制对资源访问的实例。单例在记录器中很有用,因为它们代理访问某个文件的访问,该文件只能以独占方式写入。对于诸如日志之类的东西,它们提供了一种将写入内容抽象为诸如日志文件之类的方式-您可以将缓存机制包装到单例中,等等。
还要考虑这样一种情况,您的应用程序具有许多Windows /线程/等,但是需要单点通信。我曾经使用一个控件来控制要启动我的应用程序的作业。单例负责序列化作业,并将其状态显示在程序中感兴趣的任何其他部分。在这种情况下,您可以将单例视为运行在应用程序内部的“服务器”类。
可以在Test :: Builder中找到一个单例的实际示例,该类几乎支持每个现代Perl测试模块。Test :: Builder单例存储和代理测试过程的状态和历史记录(历史测试结果,计算运行的测试次数)以及测试输出的位置。这些都是协调由不同作者编写的多个测试模块以在单个测试脚本中一起工作所必需的。
Test :: Builder单例的历史具有教育意义。调用new()
总是给您相同的对象。首先,所有数据都存储为类变量,而对象本身没有任何内容。这一直有效,直到我想自己测试Test :: Builder为止。然后,我需要两个Test :: Builder对象,一个设置为虚拟对象,以捕获并测试其行为和输出,另一个是真正的测试对象。那时,Test :: Builder被重构为一个真实的对象。单例对象存储为类数据,new()
并将始终返回它。 create()
已添加以制作新对象并启用测试。
当前,用户希望在自己的模块中更改Test :: Builder的某些行为,而让其他行为搁浅,而测试历史记录在所有测试模块中仍然是相同的。现在正在发生的事情是将整体的Test :: Builder对象分解成较小的片段(历史记录,输出,格式...),而Test :: Builder实例将它们收集在一起。现在Test :: Builder不再必须是单例。它的组成部分就像历史一样。这将单身人士的僵化必要性推低了一个层次。它为用户提供了更大的灵活性来进行混搭。较小的单例对象现在可以只存储数据,其包含的对象决定如何使用它。它甚至允许通过使用Test :: Builder历史记录和输出单例来与非Test :: Builder类一起玩。
似乎在数据的协调与行为的灵活性之间存在推拉作用,这可以通过将单例放在仅具有最小行为量的共享数据周围以确保数据完整性来缓解。
当您从数据库或文件中加载配置属性对象时,将其作为单例是有帮助的。没有理由继续读取服务器运行时不会改变的静态数据。
实施State模式时,可以使用Singleton(按照GoF书中所示的方式)。这是因为具体的状态类没有自己的状态,而是根据上下文类执行其操作。
您也可以将Abstract Factory设为单例。
setState()
负责确定状态创建策略。如果您的编程语言支持模板或泛型,则将有所帮助。可以使用Monostate模式代替Singleton 模式,在该模式下实例化状态对象最终会重用同一全局/静态状态对象。更改状态的语法可以保持不变,因为您的用户不必知道实例化的状态是Monostate。
共享资源。特别是在PHP中,有一个数据库类,一个模板类和一个全局变量仓库类。所有这些都必须由整个代码中使用的所有模块/类共享。
这是一种真正的对象用法->模板类包含正在构建的页面模板,并且通过添加到页面输出的模块对其进行成形,添加,更改。它必须作为一个实例保存,这样才能发生,数据库也是如此。使用共享的数据库单例,所有模块的类都可以访问查询并获得查询,而无需重新运行它们。
全局变量仓库单例为您提供了全局,可靠且易于使用的变量仓库。它使您的代码更加整洁。想象一下,将所有配置值都放在一个数组中,例如:
$gb->config['hostname']
或将所有语言值都放在一个数组中,例如:
$gb->lang['ENTER_USER']
在运行页面代码的最后,您得到了一个已经成熟的代码:
$template
Singleton,$gb
具有可替换的lang数组的Singleton,所有输出均已加载并准备就绪。您只需将它们替换为现在存在于成熟模板对象的页面值中的键,然后将其提供给用户即可。
这样做的最大好处是,您可以对任何内容进行任何后处理。您可以将所有语言值通过管道传输到Google翻译或其他翻译服务,然后将其取回,然后将其替换为它们的位置(例如,已翻译)。或者,您可以根据需要替换页面结构或内容字符串。
在处理可插拔模块时,我将其用于封装命令行参数的对象。主程序不知道要加载的模块的命令行参数是什么(甚至不总是知道要加载的模块)。例如,主加载A本身不需要任何参数(所以为什么它应该使用额外的指针/引用/任何我不确定的东西-看起来像污染),然后加载模块X,Y和Z。两个其中的X和Z表示需要(或接受)参数,因此他们回叫到命令行单例以告诉它要接受哪些参数,然后在运行时回叫以查明用户是否实际指定了任何参数。其中。
在许多方面,如果您每个查询仅使用一个进程,则用于处理CGI参数的单例将以类似的方式工作(其他mod_ *方法不这样做,因此在那里很糟糕-因此,说您不应如果您移植到mod_perl或其他任何世界,请在mod_cgi世界中使用单例。
也许是一个带有代码的例子。
在这里,ConcreteRegistry是扑克游戏中的一个单例,它允许整个包树的行为访问游戏的几个核心界面(即,模型,视图,控制器,环境等的外观):
http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html
埃德
1-对第一个答案的评论:
我不同意静态Logger类。这对于一个实现来说可能是实际的,但是对于单元测试来说是不可替代的。静态类不能用测试双精度代替。如果您不进行单元测试,则不会在这里看不到问题。
2-我尽量不要手工创建一个单例。我只是使用构造函数创建一个简单的对象,该构造函数使我可以将协作者注入该对象。如果需要单身人士,则可以使用依赖检查框架(Spring.NET,Unity for .NET,Spring for Java)或其他一些框架。
ILogger logger = Logger.SingleInstance();
在此方法为静态的情况下,它返回一个静态存储的ILogger实例。您使用了“依赖项注入框架”的示例。几乎所有的DI容器都是单例;它们的配置是静态定义的,最终可以从单个服务提供者接口访问或存储在单个服务提供者接口中。