Singleton的替代品是什么


114

我们有一个类,其中包含应用程序的配置信息。它曾经是一个单身人士。经过一些体系结构审查后,我们被告知删除单例。我们确实看到了在单元测试中不使用单例的一些好处,因为我们可以同时测试不同的配置。

没有单例,我们必须在代码中的所有地方传递实例。变得如此混乱,所以我们写了一个单例包装器。现在,我们正在将相同的代码移植到PHP和.NET,我想知道是否可以为配置对象使用更好的模式。

Answers:


131

谷歌测试博客有一系列的关于避免(以创建可测试的代码)辛格尔顿条目。也许这可以帮助您:

上一篇文章详细解释了如何将新对象的创建移入工厂,因此可以避免使用单例。值得一读。

简而言之,我们将所有新运营商移至工厂。我们将寿命相似的所有对象归为一个工厂。


3
***使用依赖性注射避免单身人士
贾斯汀(Justin)2009年

这些文章与Google C ++编程标准一样好!

2
好吧,不是真的。例如,“请勿使用静态方法”建议与Scott Meyers / Herb Sutters的最小接口原则完全相反。有一些有用的建议,但它们缺乏多方面的贡献。
Matthieu M.

@FrankS为什么要切换链接的顺序?起初按时间顺序排列。
cregox

@Cawas实际上不知道,那是四年多以前的事,所以我想我当时有一些理由:-)
FrankS 2013年

15

最好的方法是改用Factory模式。当您在工厂中构造类的新实例时,可以将“全局”数据插入到新构造的对象中,以引用单个实例(您将其存储在工厂类中)或通过复制相关实例数据放入新对象。

然后,所有对象都将包含用于单例的数据。我认为总体上并没有太大区别,但这可以使您的代码更易于阅读。


1
我不同意“最好的方式”的说法,但是+1是一个很好的选择。
tylermac

这种方法的问题在于,每个新对象都包含(或引用)潜在的大量数据。任何包含gob的对象上的var_dump()都会非常迅速地产生大量带有递归警告的清单。它丑陋得令人毛骨悚然,效率不高,使事情显得有些混乱。但是,我个人还没有找到更好的方法。我确实将“ factory”方法弯曲为使用__construct()来引用全局变量。感觉一切都向后弯,但是,要避免可怕的辛格尔顿……
FYA 2011年

2
@EastGhostCom:我们不妨使用单例功能,不要再试图让自己变得困难了:)
gbjbaanb 2012年

5

我可能会在这里说明显而易见的内容,但是有没有理由不能使用诸如SpringGuice这样的依赖注入框架呢?(我相信Spring现在也适用于.NET)。

这样,框架可以保存配置对象的单个副本,并且您的bean(服务,DAO,等等)不必担心查找它。

这是我通常采用的方法!


4

如果使用Spring Framework,则只能创建一个常规bean。默认情况下(或显式设置scope="singleton"),仅创建一个Bean实例,并且每次在依赖项中使用Bean或通过进行检索时都会返回该实例getBean()

您将获得单实例的优势,而无需耦合Singleton模式。


3
噢,具有讽刺意味的是-用(单粒)春豆代替单粒豆...
Zack Macomber

4

另一种方法是传递您需要的内容,而不是向对象询问事情。


4

不要累积对单个配置对象的责任,因为它会以难以理解且脆弱的非常大的对象结尾。

例如,如果您需要特定类的另一个参数Configuration,则可以更改对象,然后重新编译使用该对象的所有类。这有些问题。

尝试重构您的代码,以避免一个通用的全局Configuration对象。仅将必需的参数传递给客户端类:

class Server {

    int port;

    Server(Configuration config) {
        this.port = config.getServerPort();
    } 

}

应该重构为:

 class Server {

    public Server(int port) {
       this.port = port;
    }
 }

一个依赖注入框架将有很大的帮助在这里,但它不是stricly要求。


是的,这真的很重要。我以前做过。我的大型配置对象正在实现MailServiceConf,ServerConf ..之类的接口,而不是依赖注入框架将配置传递给类,因此我的类不依赖于大型Configuration对象。
caltuntas

1

您可以通过使用静态方法来完成单例的相同行为。史蒂夫·耶格(Steve yegge)在这篇文章中对此做了很好的解释。


实际上,本文相当不错,并且没有说您应该使用静态方法。相反,他指出静态方法也只是单例,最后他建议使用工厂方法模式:“我最后说,如果您仍然觉得需要使用单例对象,请考虑使用工厂方法模式。 ...”
FrankS

0

是否可能只包含静态方法和字段的类?我不确定您的情况到底是什么,但是可能值得研究。


1
如果该类是无状态的,则应为静态类。
AlbertoPL

1
它是用C ++语言编写的-这种模式被称为Monostate。

0

取决于所使用的工具/框架等。使用依赖项注入/ ioc工具,通常只能通过创建类的一个实例,使di / ioc容器对所需的类使用单例行为(例如IConfigSettings接口),仍然可以获得单例性能/优化。这仍然可以代替进行测试

或者,可以使用工厂来创建类,并在每次请求时返回相同的实例-但是为了进行测试,它可以返回存根/模拟的版本


0

审查将其配置为回调接口的可能性。因此,您的配置敏感代码将如下所示:

MyReuseCode.Configure(IConfiguration)

系统初始化代码如下所示:

Library.init(MyIConfigurationImpl)

0

您可以使用依赖项注入框架来减轻传递配置对象的麻烦。体面的是ninject,它具有使用代码而不是xml的优势。


0

也许也不是很干净,但是您可以将想要更改的信息传递给创建单例的方法,而不是使用

public static Singleton getInstance() {
    if(singleton != null)
        createSingleton();
        return singleton;
    }
}

您可以createSingleton(Information info)在应用程序启动时直接调用(以及在单元测试的setUp-Methods中)。


-1

单例并不是邪恶的,但是设计模式是有缺陷的。我有一个类,我只想在运行时创建它的单个实例,但是想在单元测试期间创建多个隔离的实例,以确保确定性的结果。

使用Spring等进行DI是非常好的选择,但不是唯一的选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.