什么时候不应该使用Singleton模式? (除了显而易见的)

我很清楚你想使用Singleton来提供对某个状态或服务的全局访问权限。 Singleton模式的好处不需要列举在这个问题中。

我感兴趣的是辛格尔顿看起来像是一个不错的select,但可能会回来咬你。 一次又一次,我看到书籍和海报上的作者都说Singleton模式通常是一个非常糟糕的主意。

“四人帮”指出,在下列情况下你会想要使用Singleton:

  • 必须只有一个类的实例,并且必须可以从知名接入点访问客户端。
  • 当唯一的实例应该通过子类来扩展时,客户端应该能够使用扩展实例而不修改它们的代码。

这些观点虽然值得注意,但并不是我所追求的实际观点。

有没有人有一套规则或警告,你用来评估你是否真的,真的确定你想使用单身?

摘要版本:

你知道你经常使用全局variables吗? 好吧,现在使用单身甚至更less。 实际上less得多。 几乎从不。 它们共享所有的隐含耦合(直接影响可testing性和可维护性)的全局性问题,通常“唯一可以存在”的限制实际上是一个错误的假设。

详细的答案:

对于一个单身人士来说,最重要的事情就是全球化。 这是一个暴露全局unmitigated访问的单个实例的模式。 这具有全局性编程中的所有问题,但是也采用了一些有趣的新的实现细节,否则实际价值很小(或者实际上,单实例方面可能会带来不必要的额外成本)。 实现是不一样的,当人们经常把它误认为面向对象的封装方法的时候,它实际上只是一个单一的全局实例。

你应该考虑一个单例的唯一情况是当已经有全局数据的多个实例实际上是一个逻辑或硬件访问错误。 即使这样,你通常不应该直接处理单例,而是提供一个允许实例化多次的包装接口,但只能访问全局状态。 以这种方式,你可以继续使用dependency injection ,如果你可以从类的行为中取消全局状态,那么不会在整个系统中发生彻底的改变。

但是,如果看起来好像不是依赖于全球数据,但是您有一些微妙的问题。 所以(使用包装单例的接口的dependency injection )只是一个build议,而不是一个规则。 一般来说,它还是更好的,因为至less你可以看到类依赖于单例,而在类成员函数的腹部使用:: instance()函数隐藏了这个依赖。 它还允许您提取依赖于全局状态的类并为它们进行更好的unit testing ,并且您可以传递模拟无所事事的对象,如果您将单例式对象直接依赖于单例式,则这更加困难。

当烘焙一个singleton :: instance调用,它也将一个类实例化为一个类,使得inheritance是不可能的 。 解决方法通常会打破单例的“单一实例”部分。 考虑一个情况,即有多个项目依赖于NetworkManager类中的共享代码。 即使你想让这个NetworkManager成为全局状态,也是单实例,你应该非常怀疑把它变成单例。 通过创build一个实例化自己的简单单例,基本上使其他任何项目都无法从该类中派生出来。

许多人认为ServiceLocator是一个反模式,但是我相信这比单例模式好了一半,并且有效地遮蔽了Go4模式的目的。 实现服务定位器有很多种方法,但基本的概念是把对象的构build和对象的访问分解为两个步骤。 这样,在运行时,可以连接适当的衍生服务,然后从单个全局联系点访问它。 这有一个明确的对象构造顺序的好处,也允许你从你的基础对象派生。 这在大多数情况下仍然是不好的,但是比Singleton 还差 ,并且是替代品。

一个可接受的单例(read:servicelocator)的一个具体例子可能是像SDL_mixer一样包装单实例c风格的接口。 单身人士的一个例子往往天真地实施,它可能不应该在一个日志类(当你想logging到控制台和磁盘?或者如果你想分开logging子系统时会发生什么)。

然而,依赖于全局状态的最重要的问题,当你尝试实施适当的unit testing时,总是会出现(你应该试着这么做)。 当您没有真正访问过的课程的大肠杆菌试图进行无暇的磁盘写入和阅读,连接到现场服务器并发送真实数据,或者从您的扬声器中爆出声音时,处理您的应用程序变得非常困难威利尼利。 使用dependency injection是非常好的,所以你可以在一个testing计划的情况下模拟一个无所事事的类(并且看到你需要在类的构造函数中这样做)并指向它,而不必全部你的课所依赖的全球状态。

相关链接:

  • 全局状态和单例调用的脆性
  • dependency injection避免单身人士
  • 工厂和单身人士

模式使用与出现

模式作为想法和术语是有用的,但不幸的是,当真正的模式按照需要实现时,人们似乎觉得需要“使用”模式。 通常这个单身人士是因为这是一个普遍讨论的模式而特别被强迫的。 devise你的系统的模式意识,但不要专门devise你的系统,因为他们存在就屈服于他们。 它们是有用的概念工具,但正如你不使用工具箱中的所有工具一样,你也不应该这样做。 根据需要使用它们,不多或less。

示例单实例服务定位器

#include <iostream> #include <assert.h> class Service { public: static Service* Instance(){ return _instance; } static Service* Connect(){ assert(_instance == nullptr); _instance = new Service(); } virtual ~Service(){} int GetData() const{ return i; } protected: Service(){} static Service* _instance; int i = 0; }; class ServiceDerived : public Service { public: static ServiceDerived* Instance(){ return dynamic_cast<ServiceDerived*>(_instance); } static ServiceDerived* Connect(){ assert(_instance == nullptr); _instance = new ServiceDerived(); } protected: ServiceDerived(){i = 10;} }; Service* Service::_instance = nullptr; int main() { //Swap which is Connected to test it out. Service::Connect(); //ServiceDerived::Connect(); std::cout << Service::Instance()->GetData() << "\n" << ((ServiceDerived::Instance())? ServiceDerived::Instance()->GetData() :-1); return 0; } 

一个字: testing

可testing性的标志之一是类的松散耦合,允许你隔离一个类并完全testing它。 当一个类使用单例(并且我正在谈论一个经典的单例,一个通过静态getInstance()方法强制它自己的单数),单例用户和单例变得密不可分地耦合在一起。 不再testing用户而不testing单例。

单身人士是一个testing的灾难。 由于它们是静态的,所以不能用子类来存储它们。 既然他们是全球性的,你不能轻易地改变他们指向的参考而不用重新编译或者繁重的工作。 任何使用单例的东西神奇地获得一个难以控制的东西的全球参考。 这使得很难限制testing的范围。

我看到的单身人士最大的错误是你正在devise一个单用户系统(比如说桌面程序),并且使用Singleton来处理很多事情(比如设置),然后你想成为一个多用户,就像一个网站或服务。

这与使用内部静态缓冲区的C函数在multithreading程序中使用时发生的情况类似。

我会说不惜一切代价避免单身。 它限制了应用程序的扩展。 真正分析你处理的问题,思考可扩展性,并根据你想要的应用程序的可扩展性做出决定。

在一天结束时,如果devise不正确,单身人士会成为资源瓶颈。

有时候,如果不充分理解这样做会对您的应用程序产生什么影响,则会引入此瓶颈。

在处理尝试访问单例资源的multithreading应用程序时遇到了问题,但遇到了死锁。 这就是为什么我尽量避免单身人士。

如果您在devise中引入单身人士,请确保您了解运行时影响,做一些图表并找出可能导致问题的位置。

辛格尔顿常常被用来作为一种东西,人们不会被困扰在实际需要的地方,适当的访问者正确地封装。

最终的结果是一个tarball,最终收集整个系统中的所有static 。 这里有多less人从来没有见过一个叫做Globals的类,在他们不得不使用的某些OOD代码中? 啊。

我不会在任何devise中完全避免它。 但是,一定要注意它的用法。 它可以在许多情况下成为上帝的对象,从而击败目的。

请记住,这种devise模式是解决一些问题但不是所有问题的解决scheme。 实际上,所有的devise模式都是一样的。

我不认为自己是一个有经验的程序员,但我目前的观点是,你实际上不需要单身…是的,它似乎更容易开始工作(类似于全局),但随后出现“哦,我的“一个人需要另一个实例的时刻。

您可以随时传递或注入实例,但是我并没有真正看到使用Singleton会更容易或者更有必要的情况

即使我们拒绝一切,仍然存在代码可testing性的问题

请参阅“ Singleton的替代select ”讨论中的第一个答案。