什么是单例模式的替代方案?

123
我们有一个保存应用程序配置信息的类。它曾经是单例模式。在进行一些架构审查后,我们被告知要移除单例模式。我们看到不使用单例模式在单元测试中有一些好处,因为我们可以同时测试不同的配置。
没有了单例模式,我们必须在代码的各个地方传递该实例。这变得非常混乱,所以我们编写了一个单例包装器。现在我们正在将相同的代码移植到 PHP 和 .NET,我想知道是否有更好的模式可用于配置对象。
12个回答

146

3
使用依赖注入来避免单例模式。 - Justin
2
其实不完全是这样的。例如,“不要使用静态方法”这条建议直接违反了Scott Meyers / Herb Sutters的最小接口原则。虽然有些建议很有用,但它们缺乏多个人的贡献。 - Matthieu M.
1
@FrankS 为什么你改变了链接的顺序?一开始它们按时间顺序排列得很好啊。 - cregox
@Cawas,实际上我不知道,那是四年前的事情了,所以我猜当时我有一些理由。 :-) - FrankS
@zzz777,就像我之前所说的,这里的评论区可能不是讨论这个问题的正确场所。单例确实是一个大问题,静态函数也无济于事。但如何解决你具体的问题应该是一个新的问题,或者甚至值得雇佣顾问来解决,因为它似乎是一个相当复杂的情况(如果你在德国/欧洲:http://code-quality.de)。 - FrankS
显示剩余4条评论

16

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

然后,您的所有对象都将包含以前存在于单例中的数据。总体上我认为两种方法差别不大,但这样做可以使您的代码更易于理解。


2
我不同意“最佳方式”的说法,但是因为有一个很好的替代方案所以点赞。 - tylermac
1
这种方法的问题在于,每个新对象都包含(或引用)可能是巨大数据块的内容。对其中任何一个包含大块数据的对象进行var_dump()操作,会很快地得到由递归警告自由穿插的庞大列表。它看起来非常丑陋,效率也不高,并且使事情看起来一团糟。然而,我个人没有找到更好的方法。我把“工厂”方法弯曲成使用__construct()来引用全局变量。然而,为了避免可怕的Singleton,所有东西都似乎被扭曲了。 - FYA
2
@EastGhostCom:我们不妨使用单例模式,别再让自己陷入困境了 :) - gbjbaanb

5

我可能在这里陈述了显而易见的事情,但是你为什么不能使用依赖注入框架,比如SpringGuice?(我相信现在Spring也可用于.NET)。

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

这是我通常采用的方法!


4

不要将所有责任都积累到单个配置对象中,因为这会导致一个非常庞大且难以理解和脆弱的对象。

例如,如果您需要向特定类添加另一个参数,则更改Configuration对象,然后重新编译使用它的所有类。这有点棘手。

尝试重构代码以避免使用常见、全局和大型的Configuration对象。仅向客户端类传递所需的参数:

class Server {

    int port;

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

}

应该重构为:

 class Server {

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

一个依赖注入框架在这里会很有帮助,但并非必需。


是的,这是一个非常好的观点。我以前做过类似的工作。我的大型配置对象实现了像MailServiceConf、ServerConf这样的接口...然后依赖注入框架将配置传递给类,这样我的类就不依赖于大型配置对象了。 - caltuntas

4
如果您使用Spring Framework,您只需创建一个常规bean。默认情况下(或者如果您明确设置scope="singleton"),只会创建一个bean实例,并且每次在依赖项中使用该bean或通过getBean()检索时都会返回该实例。
您可以获得单个实例的优点,而不会有Singleton模式的耦合。

5
讽刺的是 - 使用(单例)Spring beans来替换你的单例... - Zack Macomber

4

另一种方法是直接传入所需内容,而不是要求对象提供信息。


1

你可以通过使用静态方法来实现单例的相同行为。Steve Yegge在this帖子中非常好地解释了这一点。


实际上,这篇文章非常好,它并没有说你应该使用静态方法。相反,作者指出静态方法也只是单例模式的一种,并在最后推荐使用工厂方法模式:“最后我想说,如果你仍然觉得需要使用单例对象,请考虑使用工厂方法模式代替...” - FrankS

0

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

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

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


0

检查将配置作为回调接口的可能性。 这样,您的配置敏感代码将如下所示:

MyReuseCode.Configure(IConfiguration)

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

Library.init(MyIConfigurationImpl)

0

一个只包含静态方法和字段的类是可能的吗?我不确定你的具体情况是什么,但值得研究一下。


1
如果一个类是无状态的,那么它应该是一个静态类。 - AlbertoPL
1
它是用C++编写的 - 这种模式被称为单态模式。 - anon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接