language-agnostic - 什么时候不应该使用单例模式? (除了显而易见的)

标签 language-agnostic singleton design-patterns

我很清楚您想使用 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)要好得多,这样您就可以在测试计划的情况下模拟一个什么都不做的类(并看到您需要在类构造函数中执行此操作)并指出它而不必占卜所有你的类所依赖的全局状态。

    相关链接:

  • Brittleness invoked by Global State and Singletons
  • Dependency Injection to Avoid Singletons
  • Factories and Singletons

  • 模式使用与出现

    模式作为想法和术语很有用,但不幸的是,当真正按照需要实现模式时,人们似乎觉得有必要“使用”模式。通常,单例特别被强加于人,因为它是一个普遍讨论的模式。以模式意识设计你的系统,但不要仅仅因为模式存在而专门设计你的系统就屈服于它们。它们是有用的概念工具,但正如您不会仅仅因为可以使用工具箱中的每个工具一样,您也不应该对模式做同样的事情。根据需要使用它们,不多也不少。

    示例单实例服务定位器
    #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/

    相关文章:

    algorithm - 未排序数组中的前 5 个元素

    language-agnostic - 如何测试应用程序的正确编码(例如 UTF-8)

    python - 我怎样才能唯一地缩短字符串列表,使它们最多 x 个字符长

    database - 使用迁移的好处

    Python Singletons 语法以及为什么它看起来像那样?

    android - 在 build.gradle 中找不到构建工具修订版 25.0.0 rc1

    java - 单例与否。如何测量?

    ios - 制作单例的单个副本

    nhibernate - 继承上的组合 - 额外的属性去哪里了?

    design-patterns - 单例模式的性能缺点