那么Singletons不好,那又如何呢?


553

最近,关于使用(和过度使用)Singleton的问题进行了很多讨论。我也是我职业生涯早期的那些人之一。我可以看到问题出在哪里,但是,在许多情况下,我看不到一个很好的选择-而且,很少有反辛格尔顿的讨论真正提供过这样的讨论。

这是我参与的一个近期重大项目的真实示例:

该应用程序是一个胖客户端,具有许多单独的屏幕和组件,它们使用来自服务器状态的大量数据,这些数据并不经常更新。该数据基本上被缓存在Singleton“管理器”对象中-可怕的“全局状态”。想法是在应用程序中拥有一个位置来保存和同步数据,然后打开的任何新屏幕都可以从那里查询它们的大部分需求,而无需从服务器重复请求各种支持数据。不断地向服务器请求会占用太多带宽-我说的是每周要多付数千美元的互联网账单,所以这是不能接受的。

除了基本具有这种全局数据管理器缓存对象之外,还有其他合适的方法吗?当然,此对象不一定要正式是“ Singleton”,但从概念上讲,成为一个对象确实有意义。这里有什么不错的清洁选择?


10
使用Singleton应该解决什么问题?与替代方案(例如静态类)相比,解决该问题的方法更好吗?
Anon。

14
@Anon:使用静态类如何使情况更好。还有紧密耦合吗?
马丁·约克

5
@马丁:我并不是说它会让它“更好”。我建议在大多数情况下,单例是解决问题的解决方案。
Anon。

9
@Anon:不对。静态类使您(几乎)无法控制实例化,并且使多线程比Singletons更加困难(因为您必须序列化对每个方法的访问,而不仅仅是序列化实例)。单例也至少可以实现静态类不能实现的接口。静态类当然具有它们的优点,但是在这种情况下,单例无疑是两个明显弊端中的较小者。实现任何可变状态的静态类就像一个闪烁的霓虹灯“警告:不良的设计!” 标志。
亚伦诺特2011年

7
@Aaronaught:如果您只是同步对单例的访问,那么您的并发性将被破坏。在获取单例对象之后,另一个线程将进入运行状态,并且出现竞争状态,您的线程可能会中断。在大多数情况下,使用Singleton代替静态类只是拿走警告标志并思考解决问题的方法
Anon。

Answers:


809

在这里区分单个实例Singleton设计模式很重要。

单个实例只是一个现实。大多数应用程序仅设计一次使用一种配置,一次使用一个UI,一次使用一个文件系统,等等。如果要维护很多状态或数据,那么肯定您将只想拥有一个实例并使它保持尽可能长的生命周期。

Singleton 设计模式是一种非常具体的单实例类型,特别是一种:

  • 可通过全局静态实例字段访问;
  • 在程序初始化或首次访问时创建;
  • 没有公共构造函数(无法直接实例化);
  • 从不明确释放(在程序终止时隐式释放)。

正是由于这种特定的设计选择,该模式引入了一些潜在的长期问题:

  • 无法使用抽象或接口类;
  • 无法分类;
  • 整个应用程序之间的高度耦合(难以修改);
  • 难以测试(在单元测试中不能伪造/模拟);
  • 在可变状态下很难并行化(需要广泛的锁定);
  • 等等。

这些症状实际上都不是单个实例的地方病,只有Singleton模式。

您能做什么呢?只是不要使用Singleton模式。

引用问题:

想法是在应用程序中拥有一个位置来保存和同步数据,然后打开的任何新屏幕都可以从那里查询它们的大部分需求,而无需从服务器重复请求各种支持数据。不断地向服务器请求会占用太多带宽-我说的是每周要多付数千美元的互联网账单,所以这是不能接受的。

正如您所暗示的那样,这个概念有个名字,但听起来不确定。这称为缓存。如果想花哨的话,可以将其称为“离线缓存”,也可以仅称为远程数据的脱机副本。

高速缓存不必是单例。它可能需要一个单独的实例,如果你想避免获取多个缓存实例相同的数据; 但这并不意味着您实际上必须将所有内容公开给所有人

我要做的第一件事是将缓存的不同功能区域划分为单独的接口。例如,假设您基于Microsoft Access制作了世界上最差的YouTube克隆:

                          MSAccessCache
                                ▲
                                |
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

在这里,您有几个接口来描述特定类可能需要访问的数据的特定类型-媒体,用户配置文件和静态页面(如首页)。所有这一切都实现由一个大型缓存,但你设计你的个人类来接受,而不是接口,所以他们不在乎什么样的,他们有一个实例。您可以在程序启动时初始化物理实例一次,然后通过构造函数和公共属性开始传递实例(广播到特定的接口类型)。

顺便说一下,这称为依赖注入。您不需要使用Spring或任何特殊的IoC容器,只要您的通用类设计接受来自调用方的依赖关系,而不是自行实例化它们引用全局状态即可

为什么要使用基于接口的设计?三个原因:

  1. 它使代码更易于阅读;您可以从界面清楚地了解依赖类所依赖的数据

  2. 如果并且当您意识到Microsoft Access不是数据后端的最佳选择时,您可以使用更好的替代它-假设是SQL Server。

  3. 如果并且当您意识到SQL Server不是特别适合媒体的最佳选择时,您可以中断实现而不会影响系统的任何其他部分。那才是真正的抽象力量出现的地方。

如果要更进一步,则可以使用IoC容器(DI框架),例如Spring(Java)或Unity(.NET)。几乎每个DI框架都将执行其自己的生命周期管理,并且特别允许您将特定服务定义为单个实例(通常称为“单例”,但这仅是为了熟悉)。基本上,这些框架为您省去了手动传递实例的大部分繁琐工作,但这并不是绝对必要的。 您无需任何特殊工具即可实现此设计。

为了完整起见,我应该指出,以上设计实际上也不理想。在处理缓存时(实际上),实际上应该有一个完全独立的图层。换句话说,这样的设计:

                                                        +-IMedia存储库
                                                        |
                          缓存(通用)--------------- +-IProfileRepository
                                ▲|
                                | +-IPage资料库
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

这样做的好处是,Cache如果您决定进行重构,则您甚至不需要中断实例。您可以简单地通过将Media提供给它的替代实现来更改Media的存储方式IMediaRepository。如果考虑如何将它们组合在一起,您会发现它仍然只创建一个缓存的物理实例,因此您无需两次提取相同的数据。

这并不是说,世界上的每一个软件都必须按照这些严格的高内聚和松散耦合标准进行构建。它取决于项目的大小和范围,您的团队,您的预算,截止日期等。但是,如果您要问最佳设计是什么(代替单例使用),就可以了。

PS正如其他人所述,让依赖类知道它们正在使用缓存可能不是最好的主意-这是他们根本不关心的实现细节。话虽如此,整体架构仍将与上图非常相似,只是您不会将各个接口称为Caches。相反,您可以将它们命名为Services或类似名称。


131
我读过的第一篇文章实际上将DI解释为全局状态的替代方法。感谢您为此付出的时间和精力。由于这篇文章,我们所有人都过得更好。
2013年

4
为什么缓存不能为单例?如果传递它并使用依赖项注入,它不是单例吗?Singleton只是将自己限制在一个实例上,而不是如何正确访问它?请参阅我对此的看法
Erik Engheim 2013年

29
@AdamSmith:你真的读任何这个答案的?您的问题在前两段中得到了回答。单例模式!==单实例。
Aaronaught

5
@Cawas和Adam Smith-阅读您的链接我感到您没有真正阅读此答案-一切已经准备就绪。
Wilbert

19
@Cawas我觉得这个答案的实质是单实例和单例之间的区别。单例是坏的,单实例不是。依赖注入是使用单实例而不使用单例的一种很好的通用方法。
Wilbert

48

在您给出的情况下,听起来像使用Singleton不是问题,而是问题的征兆 -更大的体系结构问题。

屏幕为什么要查询缓存对象中的数据?缓存对客户端应该是透明的。应该提供一个适当的抽象来提供数据,并且该抽象的实现可能会使用缓存。

问题可能是系统各部分之间的依存关系设置不正确,这可能是系统性的。

为什么屏幕需要知道它们从何处获取数据?为什么屏幕没有提供可以满足其数据请求的对象(在其后面隐藏了缓存)?通常,创建屏幕的职责不是集中的,因此注入依赖项没有明确的意义。

同样,我们正在研究大规模的建筑和设计问题。

同样,非常重要的一点是要了解,对象的寿命可以与找到使用对象的方式完全分开。

缓存必须在应用程序的整个生命周期中都存在(才有用),因此对象的生命周期是Singleton。

但是Singleton(至少是Singleton作为静态类/属性的常见实现)的问题在于,其他使用它的类如何找到它。

对于静态Singleton实现,约定是在需要的地方简单地使用它。但这完全隐藏了依赖关系,并将两个类紧密结合在一起。

如果我们提供对类的依赖关系,则该依赖关系是显式的,所有使用类都需要知道的是可使用的合同。


2
某些屏幕可能需要大量数据,但不一定需要。而且,直到采取了定义此操作的用户操作,您才知道-还有很多很多组合。因此,这样做的方法是将一些通用的全局数据保留在客户端中并进行缓存(大多数是在登录时获取),然后再进行同步,然后后续的请求会更多地建立缓存,因为显式请求的数据倾向于在其中再次使用。同一会话。重点是减少对服务器的请求,因此需要客户端缓存。<cont>
Bobby Tables

1
<cont>它本质上是透明的。从某种意义上说,如果尚未缓存某些必需的数据,则服务器会进行回调。但是,该缓存管理器的实现(在逻辑上和物理上)都是Singleton。
鲍比表

6
我在这里与qstarin在一起:访问数据的对象不应该知道(或需要知道)数据已缓存(这是实现细节)。数据的用户仅要求提供数据(或要求提供检索数据的接口)。
马丁·约克

1
缓存本质上是实现细节。有一个用于查询数据的接口,获取数据的对象不知道它是否来自缓存。但是在此缓存管理器下面是一个Singleton。
鲍比表

2
@Bobby Tables:那么您的情况并不像看起来那样可怕。单例(假设您的意思是一个静态类,而不仅仅是具有实例的对象,该实例的寿命与应用程序一样长)仍然是有问题的。它掩盖了一个事实,即您的数据提供对象依赖于缓存提供程序。最好是显式的和外部化的。解耦它们。这是至关重要的可测性,你可以很容易地替换组件,以及高速缓存提供者是一个这种成分的例子(多久是ASP.Net支持的缓存提供者)。
2011年

45

我就这个问题写了整整一章。大多数情况下是在游戏方面,但大多数应在游戏外部应用。

tl; dr:

四个单身帮的模式有两件事:使您可以从任何地方方便地访问对象,并确保只能创建该对象的一个​​实例。在99%的时间里,您所关心的只是前半部分,而在下半部分进行购物以增加不必要的限制。

不仅如此,还有更好的解决方案可以提供方便的访问。使对象成为全局对象是解决该问题的核选择,并使其易于破坏封装。不利于全局变量的一切都完全适用于单例。

如果你使用它,只是因为你有很多的代码,需要触摸同一对象的地方,试图找到更好的方式来给它只是这些对象不能暴露在整个代码库。其他解决方案:

  • 完全抛弃它。我见过很多单例类,它们没有任何状态,只是一些辅助函数。那些根本不需要实例。只需使它们成为静态函数,或将它们移入该函数作为参数的类之一即可。Math如果您可以的话,就不需要特殊的课程123.Abs()

  • 传递它。如果方法需要其他对象,则简单的解决方案是将其传递。将一些对象传递到周围没有什么错。

  • 将其放在基类中。如果您有很多都需要访问某个特殊对象的类,并且它们共享一个基类,则可以使该对象成为基类的成员。构造它时,传入对象。现在,派生的对象都可以在需要时获取它。如果对其进行保护,则确保该对象仍保持封装状态。


1
您可以在这里总结一下吗?
妮可(Nicole)

1
完成,但我仍然鼓励您阅读整章。
优厚的

4
另一个选择:依赖注入!
Brad Cupit

1
@BradCupit他也在链接上谈到了这一点……我必须说,我仍在尝试消化所有这些。但这是我所读过的关于单例的最清晰的读物。到现在为止,我还是需要有积极的单身,就像全局变量,我是促进工具箱。现在我不知道了。munificient先生,能告诉我服务定位器是否只是一个静态工具箱?将其设置为单例(因此是工具箱)会更好吗?
cregox

1
在我看来,“工具箱”与“服务定位器”非常相似。我认为,对于大多数程序而言,是使用静态的还是将其本身做成单例的都没有那么重要。我倾向于静态,因为如果不需要,为什么要处理惰性初始化和堆分配?
2013年

21

问题本身不是全球性的。

真的,您只需要担心global mutable state。恒定状态不受副作用影响,因此问题不大。

单例的主要问题是它增加了耦合,从而使诸如测试的工作变得更加艰巨。您可以通过从其他来源(例如工厂)获取单例来减少耦合。这将使您可以将代码与特定实例解耦(尽管您与工厂的耦合程度更高(但至少工厂可以为不同阶段提供替代实现))。

在您的情况下,只要您的单例实际上实现了一个接口(这样就可以在其他情况下使用替代方法),您就可以摆脱它。

但是单例的另一个主要缺点是,一旦就位,就将它们从代码中删除,并用其他东西替换就成为了一项艰巨的任务(再次存在耦合)。

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.

那讲得通。这也使我认为我从没有真正滥用过Singleton,而是开始怀疑对它们的任何使用。但是,根据这些观点,我想不出我已经进行过任何彻底的虐待。:)
Bobby Tables

2
(您可能是说本身不是“说”)
nohat 2011年

4
@nohat:我是“ Queens English”的母语使用者,因此拒绝任何法语外观,除非我们做得更好(例如,le weekend哎呀,这是我们的其中之一)。谢谢:-)
马丁·约克

21
本身是拉丁文。
Anon。

2
@Anon:好的。那还不算太糟;
马丁·约克

19

那呢 由于没有人说:Toolbox。那就是如果你想要全局变量

通过从另一个角度看问题,可以避免单例滥用。假设一个应用程序只需要一个类的一个实例,并且该应用程序在启动时就配置了该类:为什么类本身应该负责成为一个单例呢?由于应用程序需要这种行为,因此应用程序承担此责任似乎是很合逻辑的。应用程序而不是组件应该是单例。然后,应用程序使组件实例可供任何特定于应用程序的代码使用。当应用程序使用多个这样的组件时,它可以将它们聚合到我们所谓的工具箱中。

简而言之,应用程序的工具箱是一个单例,负责配置自身或允许应用程序的启动机制对其进行配置...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

但猜猜怎么了?这是一个单身人士!

什么是单身人士?

也许这就是混乱的开始。

对我而言,单例是强制实施为仅且始终具有单个实例的对象。您可以随时随地访问它,而无需实例化它。这就是为什么它与息息相关static。为了进行比较,static除了不是实例之外,基本上是同一件事。我们不需要实例化它,甚至不需要,因为它是自动分配的。这确实会带来问题。

根据我的经验,简单地替换staticSingleton可以解决我正在进行的中型拼布袋项目中的许多问题。这仅意味着它确实可用于不良设计的项目。我觉得有太多的讨论,如果单例模式非常有用与否,我真的不能争辩,如果它确实不好。但是,总的来说,仍然有很多理由支持单例而不是静态方法

我唯一确定的缺点是单例,而当我们在忽略良好做法的情况下使用它们时。这确实不是那么容易处理。但是,不良做法可以应用于任何模式。而且,我知道,这么说太笼统了……我的意思是,这太多了。

不要误会我的意思!

简而言之,就像全局变量一样仍然应该始终避免单例。特别是因为他们被过度虐待。但是全局变量不能总是避免的,在最后一种情况下我们应该使用它们。

无论如何,除了工具箱外,还有许多其他建议,就像工具箱一样,每个建议都有其应用程序...

其他选择

  • 我刚刚阅读的有关单例最佳文章建议将Service Locator用作替代方法。对我来说,基本上就是“ 静态工具箱 ”。换句话说,使服务定位器为Singleton,您将拥有一个Toolbox。当然,这确实与避免单例的最初建议背道而驰,但这只是为了强制单例的问题是如何使用单例,而不是模式本身。

  • 其他人建议使用“ 工厂模式”作为替代方案。这是我从同事那里听到的第一个替代方法,我们很快就将其用作global var了。它肯定有其用法,但单例也是如此。

以上两种选择都是不错的选择。但这全取决于您的用法。

现在,暗示不惜一切代价避免单身人士是错误的……

  • 由于一系列原因,Aaronaught的答案建议不要使用单例。但是,所有这些都是反对其使用和滥用方式的原因,而不是直接针对模式本身。我确实同意所有那些担心的问题,我怎么能呢?我只是认为这具有误导性。

确实存在(抽象或子类的)无能,但是那又如何呢?这不是为了那个。据我所知,没有接口。高耦合也可以在那里,但这仅仅是因为它通常被使用。不必。实际上,耦合本身与单例模式无关。经过澄清,它也消除了测试的困难。至于并行化的难度,这取决于语言和平台,因此,模式也不是问题。

实际例子

我经常看到使用2,无论是赞成还是反对单身人士。Web缓存(我的情况)和日志服务

有人会说,日志记录是一个完美的单例示例,因为我引用:

  • 请求者需要一个众所周知的对象,要将请求发送到该对象。这意味着全球访问点。
  • 由于日志记录服务是多个侦听器可以注册到的单个事件源,因此只需要一个实例。
  • 尽管不同的应用程序可能登录到不同的输出设备,但是它们注册侦听器的方式始终相同。所有定制都通过侦听器完成。客户可以在不知道如何或在何处记录文本的情况下请求记录。因此,每个应用程序将完全相同地使用日志记录服务。
  • 任何应用程序都只能使用一个日志记录服务实例。
  • 任何对象都可以是日志记录请求者,包括可重用组件,因此它们不应与任何特定应用程序耦合。

尽管其他人会争辩说,一旦您最终意识到它实际上不应该只是一个实例,那么很难扩展日志服务。

好吧,我说这两个论点都是有效的。同样,这里的问题不在单例模式上。重构是否可行,取决于架构决策和权重。通常,重构是最后需要的纠正措施,这是另一个问题。


@gnat谢谢!我只是想在编辑答案中添加一些有关使用Singletons的警告...您的报价很合适!
cregox

2
很高兴您喜欢它。不能确定这是否有助于避免投票不足-读者可能很难在这篇文章中与问题中提出的具体问题联系起来,特别是考虑到先前答案中提供的出色分析
gna

@gnat是的,我知道这是一场漫长的战斗。我希望时间能证明一切。;-)
cregox

1
我对这里的总体方向表示同意,尽管对于一个似乎并不比一个极简陋的 IoC容器(甚至比Funq更基本的容器)的库可能有点过分热情。服务定位器实际上是一种反模式;它是一个有用的工具,主要用于遗留/布朗领域项目,以及“穷人的DI”,因为重构所有东西以正确使用IoC容器会太昂贵。
亚伦诺特,2013年

1
@Cawas:啊,对不起-感到困惑java.awt.Toolkit。但我的观点是一样的:Toolbox听起来像无关紧要的抓包,而不是出于单一目的的连贯类。听起来对我来说不是很好的设计。(请注意,您引用的文章来自2001年,之前依赖注入和DI容器已经普及。)
Jon Skeet 2014年

5

我的单例设计模式的主要问题是很难为您的应用程序编写好的单元测试。

与该“管理器”相关的每个组件都通过查询其单例实例来实现。而且,如果您要为此类组件编写单元测试,则必须将数据注入此单例实例,这可能并不容易。

另一方面,如果您的“经理”通过构造函数参数注入到从属组件中,而该组件不知道管理器的具体类型,则仅知道管理器实现的接口或抽象基类,则不知道该单元测试依赖项时,测试可以提供管理器的替代实现。

如果使用IOC容器配置和实例化构成应用程序的组件,则可以轻松配置IOC容器以创建“管理器”的一个实例,从而使您可以实现相同的控制全局应用程序缓存的一个实例。

但是,如果您不关心单元测试,那么单例设计模式就可以了。(但我还是不会这样做)


奈斯利解释说,这是最能说明有关测试单身问题的答案
何塞托马斯腌肠

4

从某种意义上说,任何设计计算都可以是好是,从根本上来说,单例并没有坏。它只能永远是正确的(给出预期的结果)或不正确。如果它使代码更清晰或更有效,它也可能有用或无效。

单例有用的一种情况是当它们表示一个真正独特的实体时。在大多数环境中,数据库是唯一的,实际上只有一个数据库。连接到该数据库可能很复杂,因为它需要特殊权限或遍历几种连接类型。仅出于这个原因,将连接组织为单例可能很有意义。

但是,您还需要确保单例确实是单例,而不是全局变量。当单个唯一数据库实际上是4个数据库,每个数据库分别用于生产,登台,开发和测试装置时,这一点很重要。Database Singleton将找出应该连接的数据库实例,获取该数据库的单个实例,如果需要,则将其连接,然后将其返回给调用方。

当一个单例不是一个真正的单例时(大多数程序员不高兴),这是一个延迟实例化的全局变量,没有机会注入正确的实例。

设计良好的单例模式的另一个有用功能是它通常是不可观察的。呼叫者要求连接。提供它的服务可以返回一个池对象,或者如果它正在执行测试,则可以为每个调用者创建一个新对象,或者提供一个模拟对象。


3

代表实际对象的单例模式的使用是完全可以接受的。我为iPhone写作,并且Cocoa Touch框架中有很多单例。应用程序本身由类的单例表示UIApplication。您只有一个应用程序,因此以单例形式表示它是适当的。

只要设计得当,就可以将单例用作数据管理器类。如果这是一堆数据属性,那没有比全局范围更好。如果是一组getter和setter,那会更好,但仍然不是很好。如果这是一个真正管理所有数据接口的类,包括获取远程数据,缓存,设置和拆卸……那可能非常有用。


2

单例只是面向服务的体系结构到程序中的投影。

API是协议级别的单例的示例。您可以通过本质上是单例的方式访问Twitter,Google等。那么,为什么单例在程序中变得不好呢?

这取决于您对程序的看法。如果您将程序视为服务社会,而不是随机绑定的缓存实例,那么单例就很有意义。

单例是服务访问点。紧密绑定的功能库的公共接口,可能隐藏了非常复杂的内部体系结构。

因此,我认为单身人士与工厂没有什么不同。单例可以传入构造函数参数。它可以由某些上下文创建,例如,该上下文知道如何根据所有可能的选择机制来解析默认打印机。为了进行测试,您可以插入自己的模拟。因此它可以非常灵活。

当我执行并需要一些功能时,密钥位于程序内部,可以完全放心该服务已启动并可以使用,可以访问单例。当在进程中启动了不同的线程(必须通过状态机才能视为就绪)时,这是关键。

通常,我会包装一个XxxService在class周围包装单例的类Xxx。单身人士根本不在课堂Xxx上,而是分成了另一个班级XxxService。这是因为Xxx虽然不可能,但是可以有多个实例,但是我们仍然希望Xxx在每个系统上全局访问一个实例。XxxService提供了很好的关注点分离。Xxx不必强制执行单例策略,但是我们可以Xxx在需要时用作单例。

就像是:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  

1

第一个问题,您在应用程序中发现很多错误吗?也许忘记了更新缓存,或者缓存不良或发现难以更改?(我记得一个应用程序不会更改大小,除非您也更改了颜色...不过,您可以更改颜色并保持大小)。

您要做的是拥有该课程,但要删除所有静态成员。好的,这不是必要的,但是我推荐。真的,您就像初始化普通类一样,初始化类,然后将指针传递进去。

它的工作更多,但实际上,它的混乱更少。有些地方您现在不应该更改某些内容,因为它不再具有全局性,现在无法更改。我所有的经理班都是普通班,就那样对待。


1

IMO,您的例子听起来还不错。我建议分解如下:为每个(和后面的)数据对象缓存对象;缓存对象和db访问器对象具有相同的接口。这样就可以在代码内外交换缓存。另外,它还提供了一条简单的扩展路线。

图形:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

数据库访问器和缓存可以从相同的对象或鸭子类型继承为看起来像相同的对象,无论如何。只要您可以插入/编译/测试,它仍然可以工作。

这样可以使事物脱钩,因此您无需添加和修改某些Uber-Cache对象就可以添加新的缓存。YMMV。IANAL。等等。


1

派对晚了一点,但是无论如何。

就像其他任何东西一样,Singleton是工具箱中的工具。希望您的工具箱中不仅有一把锤子。

考虑一下:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

第一种情况导致高耦合等;据我所知,第二种方式没有问题@Aaronaught正在描述。有关如何使用它的所有信息。


不同意。尽管第二种方法是“更好的”(减少耦合),但是它仍然存在DI解决的问题。您不应该依赖类的使用者来提供服务的实现-在创建类时最好在构造函数中完成。您的界面只需要参数上的最低要求。您的班级也有一个很好的机会需要操作单个实例-再次,依靠使用者来执行此规则是有风险且不必要的。
AlexFoxGill

有时,Di对于给定的任务来说是过大的杀伤力。样本代码中的方法可以是构造函数,但不一定是构造函数-无需查看具体示例的模拟参数。另外,DoSomething可以采用ISomething,而MySingleton可以实现该接口-好的,这不是示例,而是示例。
Evgeni

1

让每个屏幕都使用其构造函数中的Manager。

启动应用程序时,您将创建一个管理器实例并将其传递。

这称为“控制反转”,它使您可以在配置更改和测试中更换控制器。此外,您可以并行运行应用程序的多个实例或应用程序的多个部分(适合测试!)。最后,您的经理将死于其拥有的对象(启动类)。

因此,您的应用就像树一样构造,上面的东西拥有下面使用的所有东西。不要实现像网格这样的应用程序,每个人都认识每个人,并通过全局方法找到彼此。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.