当前位置:  开发笔记 > 编程语言 > 正文

Singleton:如何使用它

如何解决《Singleton:如何使用它》经验,为你挑选了9个好方法。

编辑:从另一个问题我提供了一个答案,链接到很多关于单身人士的问题/答案:有关单身人士的更多信息:

所以我读过Singletons的帖子:好的设计还是拐杖?
争论仍然激烈.

我认为单身人士是一种设计模式(好的和坏的).

Singleton的问题不是模式而是用户(对不起所有人).每个人和他们的父亲都认为他们可以正确地实施一个(而且从我做过的许多采访中,大多数人都做不到).此外,因为每个人都认为他们可以实现正确的Singleton,他们滥用模式并在不合适的情况下使用它(用Singletons替换全局变量!).

所以需要回答的主要问题是:

什么时候应该使用Singleton

如何正确实现Singleton

我对这篇文章的希望是,我们可以在一个地方收集(而不是谷歌和搜索多个网站)一个权威的来源,了解何时(以及如何)正确使用单身人士.同样合适的还有一份反用法和常见的不良实施清单,解释了为什么他们无法工作以及为了实现他们的弱点.


所以让球滚动:
我会举起手来说这是我用的,但可能有问题.
我喜欢"Scott Myers"在他的书"Effective C++"中处理这个主题

使用单身人士的好情况(不是很多):

记录框架

线程回收池

/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

好.让我们一起批评和其他实施.
:-)



1> 小智..:

你们所有人都错了.阅读问题.回答:

在下列情况下使用单身人士

您需要在系统中拥有一个且只有一个类型的对象

如果出现以下情况,请勿使用Singleton:

你想节省内存

你想尝试新的东西

你想炫耀你知道多少

因为其他人都在这样做(参见维基百科的货运程序员)

在用户界面小部件中

它应该是一个缓存

在字符串中

在Sessions中

我可以整天走

如何创建最好的单身人士:

越小越好.我是一个极简主义者

确保它是线程安全的

确保它永远不会为空

确保它只创建一次

懒惰还是系统初始化?符合您的要求

有时OS或JVM会为您创建单例(例如,在Java中,每个类定义都是单例)

提供析构函数或以某种方式弄清楚如何处置资源

使用少量记忆


你错了.如果您只需要一个对象,则只创建一个对象.如果没有逻辑方式可以容纳两个实例而不会不可逆地破坏应用程序,那么您应该考虑将其设置为单例.然后还有另一个方面,即全局访问:如果您不需要对实例进行全局访问,则它不应该是单例.
实际上,我认为你也不太正确.我将其改为:"如果你*需要*在系统中拥有一个且只有一个类型的对象,你需要*全局访问它"强调需要是我的 - 如果它方便的话就不要这样做,只有你必须拥有它.
关闭修改,开放延期.问题是你不能将单身扩展为双子或三元组.它被困成一个​​单身人士.
"如果你需要在系统中拥有一个且只有一个类型的对象" - "......并且永远不想在单元测试中模拟该对象."
@ enzom83:大写字母S Singleton包含确保其唯一性的代码。如果您只想要一个实例,则可能会丢失该代码并仅自己创建一个实例...从而为您节省了单个实例的内存,再加上避免了单一性执行代码所节省的内存,这也意味着如果您的需求发生变化,则牺牲创建第二个实例的能力。
我不明白为什么单例必须具有全局作用域才能有效使用...

2> jalf..:

单身人士可以让你在一个班级中结合两个不良特质.几乎在各方面都是错的.

单身人士给你:

    全局访问对象,和

    保证永远不会创建多于一个此类型的对象

第一是很简单.全局通常都很糟糕.除非我们确实需要,否则我们不应该让对象全局可访问.

第二个听起来似乎有道理,但让我们考虑一下.你最后一次**意外*创建了一个新对象而不是引用一个现有对象是什么时候?由于这是标记的C++,让我们使用该语言的一个例子.你经常不小心写

std::ostream os;
os << "hello world\n";

当你打算写

std::cout << "hello world\n";

当然不是.我们不需要针对此错误的保护,因为这种错误不会发生.如果确实如此,正确的反应是回家睡12-20个小时,希望你感觉好些.

如果只需要一个对象,只需创建一个实例.如果一个对象应该可以全局访问,那么将其设为全局对象.但这并不意味着创建它的其他实例应该是不可能的.

"只有一个实例是可能的"约束并不能真正保护我们免受可能的错误.但它确实使我们的代码很难重构和维护.因为很多时候,我们发现以后,我们确实需要一个以上的实例.我们确实有多个数据库,我们确实有多个配置对象,我们确实需要几个记录器.我们的单元测试可能希望能够在每次测试时创建和重新创建这些对象,以便采用一个常见的示例.

因此,一个单应使用当且仅当,我们需要这两个性状它提供了:如果我们需要全球性的访问(这是罕见的,因为全局一般气馁)我们需要防止任何人曾经创造的多个实例class(听起来像是一个设计问题).我能看到的唯一原因是,如果创建两个实例会破坏我们的应用程序状态 - 可能是因为该类包含许多静态成员或类似的愚蠢.在这种情况下,明显的答案是修复该课程.它不应该依赖于唯一的实例.

如果您需要对对象进行全局访问,请将其设置为全局对象std::cout.但是不要限制可以创建的实例数量.

如果你绝对需要将类的实例数限制为一个,并且无法安全地处理创建第二个实例,那么就强制执行.但是也不要让它在全球范围内可访问.

如果你确实需要两个特征,那么1)使它成为单身,2)让我知道你需要什么,因为我很难想象这样的情况.


是的,单身*可能*在那里是合理的.但我认为你刚刚证明了我的观点,它只是在非常奇特的情况下才有必要.大多数软件都不涉及除雪硬件.但我仍然不相信.我同意在您的实际应用中,您只需要其中一个.但是你的单元测试怎么样?它们中的每一个都应该独立运行,因此理想情况下它们应该创建自己的SpreaderController - 这对单例很难.最后,为什么你的同事会首先创建多个实例?这是一个现实的情景来防范?
或者你可以把它变成一个全局的,并且只得到一个单身的缺点*.使用单例,您同时将自己限制为该数据库cllass的一个实例.为什么这样?或者你可以看看为什么你有这么多的依赖项,实例化列表变得"非常长".这些都是必要的吗?是否应该将其中一些委托给其他组件?也许其中一些可以在一个结构中打包在一起,所以我们可以将它们作为单个参数传递.有很多解决方案,所有这些解决方案都优于单身人士.
你错过的一点是,虽然你的最后两个例子可以说是"唯一一个例子"的限制,但他们没有做任何理由来证明"全球可访问"的一个.为什么地球上的整个代码库能够访问电话交换机的管理单元?单身人士的观点是给你*两个*特征.如果你只需要一个或另一个,你不应该使用单身人士.
@ jalf - 我的目标只是给你一个关于Singleton在野外有用的例子,因为你无法想象; 我想你很多时候都没有把它应用到你当前的工作中.我从商业应用程序切换到雪犁编程只是因为它允许我使用Singleton.:) j/k我同意你的前提,即有更好的方法来做这些事情,你给了我很多思考.谢谢你的讨论!
使用单例(**AHEM!**)"模式"来阻止人们瞬间发送更多实例是一种简单的老愚蠢,只是为了防止人们加入这种情况.当我在我的小函数中有一个Foo类型的局部变量foo1并且只想要函数中的一个时,我并不担心有人会创建第二个Foo变量foo2并使用它而不是原始的.

3> DrPizza..:

单身人士的问题不在于他们的实施.它们将两个不同的概念混为一谈,这两个概念都不是明显可取的.

1)单身人士为对象提供全局访问机制.虽然在没有明确定义的初始化顺序的语言中它们可能稍微更线程安全或稍微更可靠,但这种用法仍然是全局变量的道德等价物.它是一个全局变量,装在一些笨拙的语法中(foo :: get_instance()而不是g_foo,比如说),但它服务于完全相同的目的(在整个程序中可访问的单个对象)并具有完全相同的缺点.

2)单身人士阻止一个类的多个实例化.很少见,IME,这种功能应该融入一个类.这通常是一个更具背景性的事情; 许多被认为是独一无二的东西真的只是恰好只有一个.IMO更合适的解决方案是只创建一个实例 - 直到您意识到需要多个实例.


同意.在现实世界中,根据某些人的说法,两个错误可能是正确的.但是在编程中,混合两个不好的想法并不会产生好的想法.

4> Paweł Hajdan..:

模式有一点:不要概括.当它们有用时,以及当它们失败时,它们都有所有的情况.

当你必须测试代码时,单身人士会很讨厌.您通常会遇到类的一个实例,并且可以选择在构造函数中打开一个门,还是在重置状态等方法之间进行选择.

其他问题是,单身实际上只不过是伪装的全局变量.当你的程序中有太多的全局共享状态时,事情往往会回归,我们都知道.

它可能使依赖性跟踪更难.当一切都取决于你的单身人士时,更难改变它,分成两个等等.你通常会坚持下去.这也妨碍了灵活性.调查一些依赖注入框架以尝试缓解此问题.


不,单身人士不仅仅是伪装的全球变量.这就是特别糟糕的原因.它将全局性(通常是坏的)与另一个概念结合起来,这个概念也是*坏的(如果程序员决定他需要一个实例,则不会让程序员实例化一个类)它们通常被*用作*作为全局变量,是的.然后他们也拖入了另一个讨厌的副作用,并削弱了代码库.
还应该指出,单身人士不必具有公共可访问性.单例很可能是库的内部,并且永远不会暴露给用户.所以他们在这个意义上不一定是"全球性的".
@William:我不得不经常有多个记录器.你不是在争论一个单身人士,而是一个普通的老人.您想知道*a*记录器始终可用.这就是全球化的意义所在.你不需要知道*没有其他记录器可以实例化*,这是单身人员强制执行的.(尝试为你的记录器编写单元测试 - 如果你可以根据需要创建和销毁它,那就容易多了,单身人士无法做到这一点)

5> Eli Courtwri..:

单身人士基本上会让你在语言中拥有复杂的全局状态,否则很难或不可能拥有复杂的全局变量.

Java特别使用单例作为全局变量的替代,因为所有内容都必须包含在类中.它与全局变量最接近的是公共静态变量,它们可以像使用全局变量一样使用import static

C++确实有全局变量,但是未定义调用全局类变量的构造函数的顺序.因此,单例允许您推迟创建全局变量,直到第一次需要该变量.

Python和Ruby等语言很少使用单例,因为您可以在模块中使用全局变量.

那么什么时候使用单身人物好/坏?几乎就是使用全局变量时好/坏的时候.



6> tenpn..:

Alexandrescu的现代C++设计具有线程安全,可继承的通用单例.

对于我的2p值,我认为为你的单身人士确定生命周期是很重要的(当它绝对有必要使用它们时).我通常不会让静态get()函数实例化任何东西,并将设置和销毁留给主应用程序的某个专用部分.这有助于突出单身人士之间的依赖关系 - 但正如上面所强调的那样,如果可能的话,最好避免使用它们.


那本书规则!

7> Mark Ransom..:

如何正确实现Singleton

有一个我从未见过的问题,这是我以前的工作遇到的问题.我们有在DLL之间共享的C++单例,并且确保单个类实例的常用机制不起作用.问题是每个DLL都有自己的一组静态变量以及EXE.如果你的get_instance函数是内联函数或者是静态库的一部分,那么每个DLL都将使用它自己的"singleton"副本.

解决方案是确保单例代码仅在一个DLL或EXE中定义,或者创建具有这些属性的单例管理器以包装实例.


哟dawg,我听说你喜欢Singletons,所以我为你的Singleton做了一个Singleton,所以你可以反模式而你反模式.

8> Rob..:

第一个例子不是线程安全的 - 如果两个线程同时调用getInstance,那个静态就是PITA.某种形式的互斥体会有所帮助.



9> 小智..:

正如其他人指出的那样,单例的主要缺点包括无法扩展它们,并失去了实例化多个实例(例如出于测试目的)的能力。

单例的一些有用方面:

    惰性或前期实例化

    方便用于需要设置和/或状态的对象

但是,您不必使用单例即可获得这些好处。您可以编写一个可以完成工作的普通对象,然后让人们通过工厂(一个单独的对象)访问它。如果需要,工厂可以担心仅实例化一个,然后重新使用它等等。同样,如果您编程到接口而不是具体的类,则工厂可以使用策略,即可以切入和切出接口的各种实现。

最后,工厂适合采用Spring等依赖项注入技术。

推荐阅读
ifx0448363
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有