什么时候不应该使用单例类,尽管这样做可能很诱人?如果我们有一份我们应该注意避免的最常见的“单项炎”实例列表,那就太好了。
11 回答
不要将单例用于可能演变成可乘资源的东西。
这可能听起来很傻,但是如果您声明某个东西是单例的,那么您就是在做出一个非常强烈的声明,即它绝对是独一无二的。你正在围绕它构建代码,越来越多。当你在数千行代码之后发现它根本不是单例时,你面前有大量工作要做,因为所有其他对象都希望 WizBang 类的“神圣”对象是单例.
典型例子:“这个应用程序只有一个数据库连接,因此它是一个单例。” - 馊主意。您可能希望将来有多个连接。最好创建一个数据库连接池并仅使用一个实例填充它。像 Singleton 一样,但所有其他代码都将具有用于访问池的可增长代码。
编辑:我知道理论上你可以将一个单例扩展到几个对象。然而,没有真正的生命周期(如池化/取消池化),这意味着已经分发的对象没有真正的所有权,即现在的多单例必须是无状态的,才能被不同的方法和线程同时使用。
大多数情况下,单身人士只是让事情变得静态。因此,您要么实际上使数据全局化,而我们都知道全局变量不好,要么您正在编写静态方法,现在这不是很OO,是吗?
这是 Steve Yegge 对单身人士为何不好的更详细的咆哮。基本上你不应该在几乎所有情况下都使用单例,你不能真正知道它永远不会在多个地方被需要。
我知道很多人的回答是“当你有多个时”等等。
由于原始海报想要一份您不应该使用单例的案例列表(而不是首要原因),我会附和:
每当您使用它时,因为您不允许使用全局!
我有多少次让初级工程师使用单例,因为他们知道我在代码审查中不接受全局变量。当我指出他们所做的只是用单例模式替换全局变量时,他们常常看起来很震惊,而他们仍然只有一个全局变量!
几年前我犯了一个大罪(谢天谢地,从那时起我已经吸取了教训)。
发生的事情是,我加入了一个桌面应用程序项目,该项目已从 VB6 转换为 .Net,并且真的一团糟。诸如 40 页(印刷)的函数和没有真正的类结构之类的东西。我构建了一个类来封装对数据库的访问。不是一个真正的数据层(还),只是一个真正的数据层可以使用的基类。在某个地方,我想到了让这个类成为单身人士的好主意。它运行了一年左右还不错,然后我们还需要为该应用程序构建一个 Web 界面。单例最终成为数据库的一个巨大瓶颈,因为所有 Web 用户都必须共享相同的连接。再次……吸取教训。
回想起来,它在短时间内可能实际上是正确的选择,因为它迫使其他开发人员在使用它时更加自律,并让他们意识到范围界定问题以前在 VB6 世界中不是问题。但我应该在几周后在我们围绕它建立太多东西之前把它改回来。
这是我的朋友亚历克斯·米勒 (Alex Miller) 的咆哮……它并没有完全列举“什么时候不应该使用单例”,但它是一篇全面、出色的帖子,并认为应该只在极少数情况下使用单例,如果有的话.
单例实际上总是一个坏主意,并且通常无用/多余,因为它们只是对体面模式的非常有限的简化。
查看依赖注入是如何工作的。它解决了同样的问题,但以一种更有用的方式——事实上,您会发现它适用于您设计的更多部分。
虽然你可以在那里找到 DI 库,但你也可以自己创建一个基本的库,这很容易。
我尝试只有一个单例 - 控制/服务定位器对象的反转。
IService service = IoC.GetImplementationOf<IService>();
使其成为噩梦的一件事是它是否包含可修改的全局状态。我在一个项目中工作,在整个地方都使用单例来解决应该以完全不同的方式解决的事情(传递策略等)。“去单例化”在某些情况下是对部分内容的重大重写系统。我会争辩说,在大多数情况下,当人们使用单例时,这只是错误的 b/c,它一开始看起来不错,但在测试中尤其是变成了一个问题。
有时,你认为只有一件事,然后你就错了。
例如,一个数据库类。您假设您只会连接到应用程序的数据库。
// Its our database! We'll never need another
class Database
{
};
可是等等!你的老板说,连接到其他一些人的数据库。假设您想将 phpbb 添加到网站,并想戳它的数据库以集成它的一些功能。我们应该创建一个新的单例还是另一个数据库实例?大多数人都同意优先使用同一类的新实例,没有代码重复。
你宁愿有
Database ourDb;
Database otherDb;
比复制过去的数据库并制作:
// Copy-pasted from our home-grown database.
class OtherGuysDatabase
{
};
这里的滑坡是你可能会停止考虑创建新的类实例,而是开始认为每个实例都有一个类型是可以的。
当您有多个应用程序在同一个 JVM 中运行时。
单例是跨整个 JVM 的单例,而不仅仅是单个应用程序。即使多个线程或应用程序似乎正在创建一个新的单例对象,但如果它们在同一个 JVM 中运行,它们都使用同一个对象。
在连接的情况下(例如),您不希望将连接本身设为单例,您可能需要四个连接,或者您可能需要多次销毁并重新创建连接。
但是您为什么不通过单个接口(即连接管理器)访问所有连接呢?