关于devise模式:何时使用Singleton?
荣耀的全球variables – 成为一个辉煌的全球课堂。 有人说打破面向对象的devise。
给我的场景,除了好的旧的logging器,它是有意义的使用单身。
在寻求真相之后,我发现实际上有很less的“可以接受的”使用单身人士的理由。
在互联网上反复出现的一个原因就是“伐木”类(你提到过)。 在这种情况下,可以使用Singleton来代替类的单个实例,因为一个日志类通常需要一遍又一遍地被一个项目中的每个类使用。 如果每个类使用这个日志logging类,dependency injection变得麻烦。
logging是一个“可接受的”单例的具体例子,因为它不影响你的代码的执行。 禁用日志logging,代码执行保持不变。 启用它,一样的。 Misko在单身人士的根本原因中说:“这里的信息是一种方式:从你的应用程序到logging器。即使logging器是全局状态,因为没有信息从logging器stream入你的应用程序,logging器是可以接受的。
我相信还有其他有效的理由。 Alex Miller在“ Patterns I Hate ”中谈到了服务定位器和客户端UI也可能是“可接受”的select。
在辛格尔顿阅读更多我爱你,但你让我失望。
辛格尔顿候选人必须满足三个要求:
- 控制对共享资源的并发访问。
- 将从系统的多个不同部分请求访问资源。
- 只能有一个对象。
如果你的build议Singleton只有一个或两个这样的要求,重新devise几乎总是正确的select。
例如,打印机假脱机程序不太可能从多个位置(“打印”菜单)调用,因此可以使用互斥锁来解决并发访问问题。
一个简单的logging器是可能有效的Singleton最明显的例子,但是这可以通过更复杂的loggingscheme来改变。
读取只应在启动时读取的configuration文件,并将其封装在Singleton中。
您需要pipe理共享资源时使用单例。 例如打印机假脱机程序。 您的应用程序应该只有一个假脱机程序实例,以避免相同资源的请求发生冲突。
或数据库连接或文件pipe理器等
只读单态存储一些全局状态(用户语言,帮助文件path,应用程序path)是合理的。 要小心使用单身人士来控制业务逻辑 – 单身几乎总是以多重的方式结束
pipe理连接(或连接池)到数据库。
我也会用它来检索和存储外部configuration文件的信息。
你使用单例的方法之一就是覆盖一个必须有一个“代理”来控制资源访问的实例。 单身汉在伐木工中performance良好,因为他们可以访问只能写入文件的文件。 对于类似于日志logging的东西,他们提供了一种方式来将写入内容抽象为日志文件 – 你可以将caching机制包装到你的单例中等等。
也想想你有一个应用程序与许多窗口/线程/等,但需要一个单一的通信点的情况。 我曾经用一个来控制我希望我的应用程序启动的工作。 单身人士负责将工作序列化,并将其状态显示给感兴趣的程序的任何其他部分。 在这种情况下,你可以将单例看作是在应用程序中运行的“服务器”类。HTH
当pipe理对整个应用程序共享的资源的访问时,应该使用单例,并且可能具有同一类的多个实例的破坏性。 确保在线程安全的情况下访问共享资源是这种模式可能至关重要的一个非常好的例子。
当使用单身人士时,你应该确保你不会意外隐藏依赖关系。 理想情况下,应用程序的初始化代码(用于C#可执行文件的static void Main(),用于java可执行文件的static void main())的初始化代码将被设置为singletons(像应用程序中的大多数静态variables),然后传递给所有其他需要它的实例化类。 这有助于保持可测性。
一个实例可以在Test :: Builder中find,这个类支持所有现代的Perltesting模块。 Test :: Builder单例存储和代理testing过程的状态和历史logging(历史testing结果,统计testing运行的次数),以及testing输出结果等。 这些都是协调由不同作者编写的多个testing模块在单个testing脚本中一起工作所必需的。
Test :: Builder的单例的历史是教育性的。 调用new()
总是给你相同的对象。 首先,所有数据都存储为类variables,而对象本身没有任何内容。 这工作,直到我想testing自己的Test :: Builder。 然后,我需要两个Test :: Builder对象,一个设置为虚拟对象,捕获并testing它的行为和输出,另一个是真正的testing对象。 那时Test :: Builder被重构成一个真实的对象。 单例对象被存储为类数据,而new()
将始终返回它。 create()
被添加来创build一个新的对象并启用testing。
目前,用户希望在自己的模块中改变Test :: Builder的一些行为,但是保留其他的行为,而testing历史在所有testing模块中保持共同。 现在发生的事情是整体的Test :: Builder对象正在被分解成更小的片段(历史,输出,格式…),并将Test :: Builder实例收集在一起。 现在Test :: Builder不再是一个单身人士。 它的组成部分,如历史,可以。 这推动了一个单身人士的不灵活的必要性。 它为用户提供了更多的灵活性,以混合和匹配件。 较小的单体对象现在可以存储数据,其中包含的对象决定如何使用它。 它甚至允许一个非Test :: Builder类通过使用Test :: Builder历史logging和输出单例。
似乎在数据的协调和行为的灵活性之间存在推拉,可以通过将单身人士尽可能less的行为放在共享的数据上来保证数据的完整性。
正如大家所说,共享资源 – 特别是不能处理并发访问的东西。
我看到的一个具体例子是Lucenesearch索引编写器。
从数据库或文件加载configuration属性对象时,它有助于将其作为单例; 没有理由继续重新读取服务器运行时不会改变的静态数据。
我认为单身人士的使用可以被认为与数据库中的多对一关系相同。 如果你的代码有很多不同的部分需要使用一个对象的单个实例,那么使用单例就是有意义的。
共享资源。 特别是在PHP中。 一个数据库类,一个模板类,一个全局variables仓库类。 所有代码都必须被所有在整个代码中使用的模块/类共享。
这是一个真正的对象使用 – >模板类包含正在构build的页面模板,并通过添加到页面输出的模块进行修改,添加和更改。 它必须作为一个单一的实例保存,这样才能发生,数据库也一样。 使用共享的db单例,所有模块的类都可以访问查询并获取它们,而无需重新运行它们。
全局variables仓库单例为您提供了一个全局的,可靠的,易于使用的variables仓库。 它整理你的代码很多。 想象一下,像$gb->config['hostname']
这样的单例中的数组中的所有configuration值,或者像$gb->lang['ENTER_USER']
这样的数组中具有所有的语言值。 在运行页面代码的最后,你会得到一个现在成熟的$template
singleton,一个$gb
singleton,它有lang数组来替代它,并且所有的输出都被加载并准备好了。 您只需将它们replace成现在存在于成熟模板对象的页面值中的键,然后将其提供给用户。
这样做的好处是你可以做任何你喜欢的任何后处理。 您可以将所有语言值input到谷歌翻译,或者其他翻译服务中,然后将其取回,并将其replace为他们的位置,例如翻译。 或者,您可以根据需要replace页面结构或内容string。
将特定的基础设施问题configuration为单例或全局variables可能非常实用。 我最喜欢的例子是dependency injection框架,它使用单例作为框架的连接点。
在这种情况下,您正在依赖基础架构来简化库的使用并避免不必要的复杂性。
你可以在实现状态模式时使用Singleton(以GoF书中所示的方式)。 这是因为具体的国家阶级没有自己的国家,并根据上下文的类别来执行他们的行为。
你也可以使抽象工厂成为一个单身人士。
在处理可插入模块时,我将它用于封装命令行参数的对象。 主程序不知道什么命令行参数是加载模块(并不总是知道什么模块正在加载)。 例如主负载A,它本身不需要任何参数(所以为什么它需要一个额外的指针/参考/任何,我不知道 – 看起来像污染),然后加载模块X,Y和Z. (比如X和Z)需要(或者接受)参数,所以他们回到命令行单例告诉它接受什么参数,并且在运行时他们回叫,以确定用户是否真的指定了他们。
在很多情况下,处理CGI参数的单例如果每个查询只使用一个进程,那么它的工作方式也是类似的(其他mod_ *方法不这样做,所以这样做会很糟糕 – 因此,不要在mod_cgi世界中使用单例,以防移植到mod_perl或任何世界)。
也许是代码的一个例子。
在这里,ConcreteRegistry是一个扑克游戏中的单例,它允许包树上的行为访问游戏的less数核心接口(即模型,视图,控制器,环境等的外观):
http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html
埃德。
1 – 对第一个答案的评论:
我不同意一个静态logging器类。 这对于实现来说可能是实用的,但是对于unit testing来说它是不可替代的。 一个静态类不能被一个testingdoublereplace。 如果你没有进行unit testing,你不会在这里看到问题。
2 – 我尽量不要手工创build一个单身人士。 我只是用构造函数创build一个简单的对象,允许我将合作者注入到对象中。 如果我需要一个单例,我会使用一个依赖关系框架(Spring.NET,Unity for .NET,Spring for Java),或者其他的。