什么是单例的替代品

Posted

技术标签:

【中文标题】什么是单例的替代品【英文标题】:What's Alternative to Singleton 【发布时间】:2010-11-21 00:02:28 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

仅包含静态方法和字段的类是否可行?我不确定您的具体情况,但可能值得研究。

【讨论】:

如果类是无状态的,应该是静态类。 它在 C++ 中 - 该模式被称为 Monostate。【参考方案2】:

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

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

【讨论】:

我不同意“最佳方式”的说法,但 +1 是一个不错的选择。 这种方法的问题是每个新对象都包含(或引用)可能是大量数据的数据。 var_dump() 在任何这些包含 gob 的对象上都会很快产生大量列表,其中包含 recursion 警告。它非常丑陋,效率不高,而且让事情看起来很混乱。但是,我个人还没有找到更好的方法。我确实将“工厂”方法转变为使用 __construct() 来引用全局。然而,为了避免可怕的单身人士,感觉一切都向后弯曲...... @EastGhostCom:我们不妨使用单例,不要再为难自己了:)【参考方案3】:

我可能会在这里说明显而易见的问题,但是您是否有理由不能使用诸如 Spring 或 Guice 之类的依赖注入框架? (我相信 Spring 现在也可用于 .NET)。

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

这是我通常采取的方法!

【讨论】:

【参考方案4】:

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

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

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

【讨论】:

【参考方案5】:

如果你使用Spring Framework,你可以创建一个普通的bean。默认情况下(或者如果您显式设置scope="singleton")只创建一个bean 实例,并且每次在依赖项中使用该bean 或通过getBean() 检索该bean 时都会返回该实例。

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

【讨论】:

哦,讽刺的是 - 使用(单例)Spring bean 来代替您的单例...【参考方案6】:

Google Testing blog 有一系列关于避免 Singleton 的条目(为了创建可测试的代码)。也许这可以帮助你:

Using dependency injection to avoid singletons Singletons are Pathological Liars Root Cause of Singletons Where have all the Singletons Gone?

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

简而言之,我们将所有新操作员转移到工厂。 我们将所有具有相似生命周期的对象分组到一个工厂中。

【讨论】:

*** 使用依赖注入避免单例 这些文章和谷歌C++编程标准一样好! 嗯,不是真的。例如,“不要使用静态方法”的建议直接违背了 Scott Meyers / Herb Sutters 的最小接口原则。有一些有用的建议,但它们缺乏多种思想的贡献。 @FrankS 为什么你改变了链接的顺序?起初它的时间顺序很好。 @Cawas 其实不知道,那是四年多以前的事了,所以我想我当时有一些原因:-)【参考方案7】:

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

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

【讨论】:

【参考方案8】:

另一种方法是传入你需要的东西,而不是向对象索要东西。

【讨论】:

【参考方案9】:

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

MyReuseCode.Configure(IConfiguration)

系统初始化代码如下:

Library.init(MyIConfigurationImpl)

【讨论】:

【参考方案10】:

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

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

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

class Server 

    int port;

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


应该重构为:

 class Server 

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

依赖注入框架在这里会有很大帮助,但不是必须的。

【讨论】:

是的,这真的很好。我以前做过。我的大配置对象实现了 MailServiceConf、ServerConf 等接口,而依赖注入框架将配置传递给类,因此我的类不依赖于大配置对象。【参考方案11】:

您可以使用依赖注入框架来减轻传递配置对象的痛苦。一个不错的是ninject,它的优点是使用代码而不是xml。

【讨论】:

【参考方案12】:

您可以使用静态方法完成与单例相同的行为。 Steve yegge 在this 帖子中解释得很好。

【讨论】:

其实这篇文章还是不错的,并没有说应该改用静态方法。相反,他说静态方法也只是单例,最后他建议使用工厂方法模式:“我最后说,如果您仍然觉得需要使用单例对象,请考虑使用工厂方法模式。 ...”【参考方案13】:

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

使用 Spring 等的 DI 是一个很好的选择,但不是唯一的选择。

【讨论】:

以上是关于什么是单例的替代品的主要内容,如果未能解决你的问题,请参考以下文章

为啥 JavaFX 是单例的?

单例和多例的区别

单例和多例的区别

单例和多例的区别

单例与多例的理解

存储库是单例的还是静态的还是都不是?