我很清楚您想使用 Singleton 来提供对某些状态或服务的全局访问点。单例模式的好处不需要在这个问题中一一列举。
我感兴趣的是 Singleton 一开始看起来是一个不错的选择,但可能会反过来咬你的情况。一次又一次,我在 SO 上的书籍和海报中看到作者说单例模式通常是一个非常糟糕的主意。
四人帮指出,您将在以下情况下使用单例:
这些要点虽然确实值得注意,但并不是我所寻求的实用要点。
有没有人有一套规则或警告可以用来评估您是否是 真的,真的确定要使用单例吗?
最佳答案
摘要版本:
你知道你使用全局变量的频率吗?好的,现在甚至更少使用单例。事实上要少得多。几乎从不。它们共享全局变量与隐藏耦合(直接影响可测试性和可维护性)的所有问题,并且通常“只有一个可以存在”的限制实际上是一个错误的假设。
详细解答:
关于单例,最重要的一点是它是全局状态。是曝光图案全局无限制访问的单个实例 .这具有全局变量在编程中的所有问题,但也采用了一些有趣的新实现细节,否则实际值(value)很少(或者,实际上,它可能会因单实例方面而带来不必要的额外成本)。实现是不同的,当它实际上只是一个花哨的全局单实例时,人们经常将其误认为是面向对象的封装方法。
您应该考虑使用单例的唯一情况是,当拥有多个已经全局数据的实例实际上是逻辑或硬件访问错误时。即便如此,您通常也不应该直接处理单例,而是提供一个包装器接口(interface) 是 允许根据需要多次实例化,但只能访问全局状态。这样您就可以继续使用 dependency injection 如果您可以从类的行为中取消全局状态,那么它就不会对您的系统产生彻底的改变。
然而,这有一些微妙的问题,当它看起来好像您不依赖全局数据时,但您确实依赖。因此(使用包装单例的接口(interface)的 dependency injection )只是一个建议而不是规则。一般来说,它仍然更好,因为至少您可以看到该类依赖于单例,而仅在类成员函数的腹部使用::instance() 函数隐藏了这种依赖关系。它还允许您根据全局状态提取类并做得更好 unit tests 对他们来说,你可以传入模拟什么都不做的对象,如果你把对单例的依赖直接放到类中,这要困难得多。
烘焙单例时 ::实例 调用也将自身实例化为您创建的类 继承不可能 .变通方法通常会破坏单例的“单实例”部分。考虑这样一种情况,您有多个项目依赖 NetworkManager 类中的共享代码。即使你希望这个 NetworkManager 是全局状态和单实例,你也应该非常怀疑将它变成单例。通过创建一个实例化自身的简单单例,您基本上使任何其他项目都无法从该类派生。
许多人认为 ServiceLocator作为一种反模式,但是我相信它比单例模式好半步,并且有效地掩盖了 Go4 模式的目的。实现服务定位器的方法有很多种,但基本概念是将对象的构建和对象的访问分解为两个步骤。这样,在运行时,您可以连接适当的派生服务,然后从单个全局联系点访问它。这具有明确的对象构造顺序的好处,并且还允许您从基础对象派生。由于大多数所述原因,这仍然很糟糕,但它是 少比 Singleton 差,是一种替代品。
可接受的单例(读取:servicelocator)的一个具体示例可能是包装单实例 c 样式接口(interface),如 SDL_mixer。单例的一个例子经常天真地实现,它可能不应该出现在日志类中(当你想登录到控制台和磁盘时会发生什么?或者如果你想单独记录子系统。)
然而,依赖全局状态的最重要问题几乎总是在您尝试实现正确的 时出现。 unit testing (你应该尝试这样做)。当您无法真正访问的类的核心试图进行无限制的磁盘写入和读取,连接到实时服务器并发送真实数据或从扬声器中发出声音时,处理您的应用程序变得非常困难无所谓。使用依赖注入(inject)要好得多,这样您就可以在测试计划的情况下模拟一个什么都不做的类(并看到您需要在类构造函数中执行此操作)并指出它而不必占卜所有你的类所依赖的全局状态。
相关链接:
模式使用与出现
模式作为想法和术语很有用,但不幸的是,当真正按照需要实现模式时,人们似乎觉得有必要“使用”模式。通常,单例特别被强加于人,因为它是一个普遍讨论的模式。以模式意识设计你的系统,但不要仅仅因为模式存在而专门设计你的系统就屈服于它们。它们是有用的概念工具,但正如您不会仅仅因为可以使用工具箱中的每个工具一样,您也不应该对模式做同样的事情。根据需要使用它们,不多也不少。
示例单实例服务定位器
#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;
}
关于language-agnostic - 什么时候不应该使用单例模式? (除了显而易见的),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4074154/