什么是单例的替代品
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 是一个很好的选择,但不是唯一的选择。
【讨论】:
以上是关于什么是单例的替代品的主要内容,如果未能解决你的问题,请参考以下文章