这里是一个我最近参与的重大项目的真实示例:
该应用程序是一个胖客户端,具有许多单独的屏幕和组件,它们使用来自服务器状态的大量数据,该数据很少更新。该数据基本上被缓存在Singleton“管理器”对象中-可怕的“全局状态”。想法是在应用程序中放置一个位置,以保存和同步数据,然后打开的任何新屏幕都可以从那里查询它们的大部分需求,而无需从服务器重复请求各种支持数据。不断向服务器请求会占用太多带宽-我说的是每周要多付数千美元的互联网账单,所以这是不可接受的。
是否有其他方法比这里基本适用有这种全局数据管理器缓存对象?当然,此对象不一定要正式是“ Singleton”,但从概念上讲,成为一个对象确实有意义。这里有什么好的替代方法?
#1 楼
在此处区分单个实例和Singleton设计模式非常重要。单个实例只是一个现实。大多数应用仅设计为一次使用一种配置,一次使用一个UI,一次使用一个文件系统,等等。如果要维护很多状态或数据,那么您肯定会只想拥有一个实例并使其保持尽可能长的生命期。
Singleton设计模式是一种非常特殊的单一类型。实例,具体来说就是:
可通过全局的静态实例字段访问;
在程序初始化或首次访问时创建;
没有公共构造函数(无法直接实例化);
永远不要显式释放(在程序终止时隐式释放)。
由于这种特定的设计选择,该模式带来了一些潜在的长期问题:
无法使用抽象或接口类;
无法子类;
应用程序之间的高度耦合(难以修改);
难以测试(可以t伪造/模拟在单元测试中);
在可变状态下难以并行化(需要广泛的锁定);
等等。
这些症状都没有单个实例实际上是地方性的,只是Singleton模式。
相反,您可以做什么?根本就不要使用Singleton模式。
引用以下问题:
这个想法是在应用程序中保留一个位置,以保存数据并同步,然后打开的任何新屏幕都可以从那里查询它们的大部分需求,而无需从服务器重复请求各种支持数据。不断地向服务器请求会占用太多带宽-我说的是每周要多付数千美元的互联网账单,所以这是不可接受的。
正如您所暗示的那样,这个概念有一个名字,但听起来不确定。它称为缓存。如果想花哨的话,可以将其称为“脱机缓存”,也可以仅称为远程数据的脱机副本。
缓存不必是单例。如果要避免为多个缓存实例获取相同的数据,则可能需要是一个实例。但这并不意味着您实际上必须将所有内容都暴露给所有人。
我要做的第一件事是将缓存的不同功能区域划分为单独的接口。例如,假设您基于Microsoft Access制作了世界上最差的YouTube克隆:
MSAccessCache ▲ | +-----------------+-----------------+ | | | IMediaCache IProfileCache IPageCache | | | | | | VideoPage MyAccountPage MostPopularPage
这里有多个接口描述特定类可能的特定数据类型需要访问-媒体,用户个人资料和静态页面(例如首页)。所有这些都是由一个大型缓存实现的,但是您可以设计自己的类来代替接受接口,因此它们不必关心它们具有哪种实例。您可以在程序启动时初始化一次物理实例,然后才开始通过构造函数和公共属性来传递实例(广播到特定的接口类型)。
这被称为依赖注入。道路;您不需要使用Spring或任何特殊的IoC容器,只要您的通用类设计接受调用方的依赖即可,而不必自己实例化它们或引用全局状态。
为什么要您使用基于接口的设计?三个原因:
它使代码更易于阅读;您可以从接口中清楚地了解依赖类所依赖的数据。
如果并且当您意识到Microsoft Access不是数据后端的最佳选择时,可以用更好的东西代替它-让我们说说SQL Server。
如果并且当您意识到SQL Server不是专门用于媒体的最佳选择时,则可以在不影响系统任何其他部分的情况下中断实现。这才是真正的抽象力量所在。
如果想进一步发展,则可以使用Spring(Java)或Unity(.NET)等IoC容器(DI框架)。 。几乎每个DI框架都将执行其自己的生命周期管理,并且特别允许您将特定服务定义为单个实例(通常称为“单例”,但这仅是为了熟悉)。基本上,这些框架为您省去了手动传递实例的大部分繁琐工作,但并非绝对必要。您无需任何特殊工具即可实现此设计。
出于完整性考虑,我应该指出,上述设计实际上也不理想。在处理高速缓存时(实际上),实际上应该有一个完全独立的层。换句话说,这样的设计:这样做的好处是,如果您决定重构,甚至不需要分解
Cache
实例。您可以简单地通过将IMediaRepository
的替代实现提供给Media来更改Media的存储方式。如果考虑一下如何将它们组合在一起,您会发现它仍然只创建一个缓存的物理实例,因此您无需两次提取相同的数据。说世界上的每一个软件都需要按照这些高凝聚力和松散耦合的严格标准进行设计; 附言正如其他人所述,让依赖类知道它们正在使用缓存可能不是最好的主意-这是他们根本不关心的实现细节。话虽这么说,总体架构仍与上图非常相似,只是您不会将各个接口称为“缓存”。相反,您可以将它们命名为Services或类似名称。
评论
我读过的第一篇文章实际上将DI解释为全局状态的替代方法。感谢您为此付出的时间和精力。由于这篇文章,我们所有人都过得更好。
–MrLane
2013年1月23日在1:11
为什么缓存不能为单例?如果传递它并使用依赖项注入,它不是单例吗? Singleton只是将自己限制在一个实例上,而不是如何正确访问它?请参阅我对此的看法:assoc.tumblr.com/post/51302471844/the-misunderstood-singleton
– Erik Engheim
2013年9月17日下午13:29
@AdamSmith:您真的读过这个答案吗?您的问题在前两段中得到了回答。单例模式!==单实例。
– Aaronaught
2013年9月18日0:09
@Cawas和Adam Smith-阅读您的链接我觉得您没有真正读过这个答案-一切已经准备就绪。
–威尔伯特
13年13月13日在12:56
@Cawas我觉得这个答案的实质是单实例和单例之间的区别。单例是坏的,单实例不是。依赖注入是使用单实例而不使用单例的一种很好的通用方法。
–威尔伯特
13年13月13日在13:29
#2 楼
在您给出的情况下,听起来好像不是使用Singleton而是问题的症状-一个更大的体系结构问题。为什么屏幕查询缓存对象以获取数据?缓存对客户端应该是透明的。应该提供一个适当的抽象来提供数据,并且该抽象的实现可能会使用缓存。
为什么屏幕需要知道它们从何处获取数据?为什么屏幕没有提供可以满足其数据请求的对象(在其后面隐藏了缓存)?通常,创建屏幕的职责不是集中的,因此注入依赖项没有明确的意义。
同样,我们正在研究大规模的建筑和设计问题。
此外,了解对象的寿命可以与方法完全脱节非常重要。找到该对象供使用。
缓存必须在应用程序的整个生命周期中都有效(有用),因此该对象的生命周期是Singleton。
但是Singleton(至少是Singleton作为静态类/属性的常见实现)的问题在于,使用它的其他类如何找到它。
使用Singleton静态实现,约定是只需在需要的地方使用它。但这完全隐藏了依赖关系,并将这两个类紧密地结合在一起。使用。
评论
某些屏幕可能需要但不一定需要大量数据。而且,直到采取了定义此操作的用户操作时,您才知道-还有很多很多组合。因此,这样做的方法是将一些通用的全局数据保留在客户端中并进行缓存(大多数是在登录时获取),然后再进行后续请求,从而增加了缓存,因为显式请求的数据往往会在其中再次使用。同一会话。重点是减少对服务器的请求,因此需要客户端缓存。 <续>
– Bobby Tables
2011年1月27日,0:26
– Bobby Tables
11年1月27日,0:27
我在这里与qstarin在一起:访问数据的对象不应知道(或需要知道)数据已缓存(这是实现细节)。数据的用户仅要求提供数据(或要求提供检索数据的接口)。
–马丁·约克
2011年1月27日,0:30
缓存本质上是实现细节。有一个用于查询数据的接口,获取数据的对象不知道它是否来自缓存。但是在此缓存管理器下面是一个Singleton。
– Bobby Tables
2011年1月27日,0:52
@Bobby Tables:那么您的情况并不像看起来那样可怕。单例(假设您的意思是一个静态类,而不仅仅是具有实例的对象,该实例的寿命与应用程序一样长)仍然是有问题的。它掩盖了一个事实,即您的数据提供对象依赖于缓存提供程序。如果那是明确的和外部的,那会更好。解耦它们。对于可测试性而言,可以轻松替换组件非常重要,并且缓存提供程序就是此类组件的典型示例(ASP.Net支持的缓存提供程序的频率)。
–quentin-starin
2011-1-27的3:24
#3 楼
我就这个问题写了整整一章。tl; dr:
“四人帮”模式有两件事:给您带来方便从任何地方访问对象,并确保只能创建该对象的一个实例。 99%的时间,您所关心的只是上半部分,而沿后半部分搬运它会增加不必要的限制。
不仅如此,还有更好的解决方案可为您提供方便访问。使对象成为全局对象是解决该问题的核选项,并使其易于破坏封装。不利于全局变量的所有内容都完全适用于单例。
如果您只是因为在代码中有很多需要触摸同一对象的地方而使用它,请尝试寻找一种更好的方法它仅用于那些对象,而不会将其暴露给整个代码库。其他解决方案:
完全抛弃它。我见过很多单例类,它们没有任何状态,只是一些辅助函数。那些根本不需要实例。只需使它们成为静态函数,或将它们移入该函数作为参数的类之一即可。如果只需要
Math
,就不需要特殊的123.Abs()
类。将其传递给周围。如果方法需要其他对象,则简单的解决方案是将其传递。将一些对象传递给周围没有任何问题。
将其放在基类中。如果您有很多都需要访问某些特殊对象的类,并且它们共享一个基类,则可以使该对象成为基类的成员。构造它时,传入对象。现在派生的对象都可以在需要时获取它。如果对其进行保护,则确保该对象仍保持封装状态。
评论
另一个选择:依赖注入!
–布拉德·库皮(Brad Cupit)
2011年2月2日,在2:12
@BradCupit他也在链接上谈到了这一点……我必须说,我仍在尝试消化所有这些。但这是我读过的关于单例的最清晰的读物。到目前为止,我很肯定需要单例,就像全局变量一样,并且我正在推广Toolbox。现在我不知道了。 munificient先生,您能告诉我服务定位器是否只是一个静态工具箱?使其成为单例(因此是工具箱)会更好吗?
– Cregox
13年13月13日在12:27
在我看来,“工具箱”与“服务定位器”非常相似。我认为,对于大多数程序而言,是使用静态的,还是使其本身成为单例的,都不那么重要。我倾向于静态,因为如果不需要的话,为什么还要处理惰性初始化和堆分配呢?
–丰富
13年13月13日在22:36
好吧,如果工具箱确实是“单人服务定位器”,出于同样的原因,我宁愿选择它,也比静态成员更喜欢单身人士。但是,与任何模式一样,它应该用于创建的内容,通常没有别的用途……我认为这就是所有混乱的来源。
– Cregox
13年11月14日在10:53
“传递一些物体没有错。”如果物体很大,传递它怎么没错呢? (按值)
–玛丽安(MarianPaździoch)
16年4月29日在9:21
#4 楼
问题本身不是全局状态。真的,您只需要担心
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
2011年1月27日,0:17
晚会晚了,但这是唯一重要的答案。全局状态并不是一个问题,它无处不在,这是当今大多数应用程序的构建方式。服务容器是受管理的全局状态。全局状态的核心问题是,您永远无法知道谁对服务进行了CRUD,以及它是如何构建的。 Laravel的服务定义明确,只能由做相同事情并且具有跟踪它们发生的情况的清晰方法的服务替代。只要该全局状态是100%可预测的,就没有问题。
–丹尼尔·西蒙斯(Daniel Simmons)
7月16日9:34
@DanielSimmons是当今大多数应用程序的构建方式:是吗? WEB服务的重点是服务器上没有任何状态。状态作为单独的服务(即DB)进行管理。可以解决我上面提到的问题,因为它已完全消除了耦合,因此可以轻松地将服务替换为另一服务(相对而言),这很好。
–马丁·约克
7月16日17:16
@DanielSimmons但这不是这个问题。这与应用程序中的全局状态有关,或者这就是我解释问题的方式。因此,如果您有一个连接到数据库的全局对象,那么现在也会遇到同样的问题。您现在已耦合到该对象。问题是如何注入连接到外部状态的对象?您可能需要其他对象(测试/质量检查/生产)。您要在计算机上测试本地测试,甚至是实际连接到真实源或某些psedu源,从而允许您快速设置测试中的状态,甚至可能有所不同。
–马丁·约克
7月16日17:18
@DanielSimmons在Java之类的语言中,他们已经建立了庞大的框架来解决单例依赖注入的问题。
–马丁·约克
7月16日17:23
#5 楼
那呢由于没有人说:工具箱。那就是您想要全局变量的情况。通过从另一个角度查看问题,可以避免单例滥用。假设一个应用程序只需要一个类的一个实例,并且该应用程序在启动时就配置了该类:为什么该类本身应该成为一个单例呢?由于应用程序需要这种行为,因此应用程序承担这种责任似乎是很合逻辑的。应用程序而不是组件应该是单例。然后,应用程序使组件实例可供任何特定于应用程序的代码使用。当应用程序使用多个这样的组件时,它可以将它们聚合到我们所谓的工具箱中。简单来说,应用程序的工具箱是一个单例,负责配置自身或允许应用程序启动配置它的机制...
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
基本上是同一件事,只是它不是实例。我们不需要实例化它,甚至不需要,因为它是自动分配的。确实会带来问题。根据我的经验,简单地将
static
替换为Singleton可以解决我正在进行的中型拼布袋项目中的许多问题。这仅意味着它确实对不良设计的项目有一定的用途。我认为,关于单例模式是否有用的讨论太多了,我不能真的争论它是否确实不好。但是,总的来说,仍然有很多理由支持单例而不是静态方法。我唯一确定的缺点是单例,而当我们在忽略良好做法的情况下使用它们时。这确实不是那么容易处理。但是,不良做法可以应用于任何模式。而且,我知道说这个太笼统了...我的意思是说太多了。
不要误会我的意思!
简单地说,就像全局变量一样,仍然应该始终避免单例。特别是因为他们被过度虐待。但是始终不能避免使用全局变量,因此我们应该在最后一种情况下使用它们。
无论如何,除工具箱外,还有许多其他建议,就像工具箱一样,每个建议都有其应用程序。 ..
其他替代方案
我刚刚阅读的有关单例的最佳文章建议将Service Locator作为替代方案。对我来说,如果可以的话,这基本上是一个“静态工具箱”。换句话说,使服务定位器为Singleton,您将拥有一个Toolbox。当然,这确实与避免单例的最初建议背道而驰,但这只是为了强制单例的问题是如何使用单例,而不是模式本身。这是我从同事那里听到的第一个替代方法,我们很快就将其作为全局变量使用了。它肯定有它的用法,但单例也是如此。
以上两种选择都是不错的选择。但这一切都取决于您的用法。
现在,暗示应该不惜一切代价避免单身人士是错误的...
确实存在(抽象或子类的)无能,但是那又如何呢?这不是为了那个。据我所知,没有接口。高耦合也可以在那里,但这仅仅是因为它是常用的。不必。实际上,耦合本身与单例模式无关。经过澄清,它也消除了测试的难度。至于并行化的难度,这取决于语言和平台,因此,模式也不是问题。
实际示例
我经常看到使用2,赞成和反对单身人士。 Web缓存(我的情况)和日志服务。
日志记录(有些人会争论)是一个完美的单例示例,因为我引用:
请求者需要一个众所周知的对象,将请求发送到该对象。这意味着有一个全局访问点。
由于日志记录服务是单个事件源,可以向多个侦听器注册该事件源,所以只需要一个实例。
尽管不同的应用程序可能会记录到不同的输出设备,他们注册听众的方式始终相同。所有定制都通过侦听器完成。客户可以在不知道如何或在何处记录文本的情况下请求记录。因此,每个应用程序都将以完全相同的方式使用日志记录服务。
任何应用程序都只能摆脱一个日志记录服务实例。
任何对象都可以是日志记录请求者,包括可重用的组件,因此,它们不应与任何特定的应用程序耦合。
其他人会争辩说,一旦您最终意识到实际上不应该扩展日志服务,将很难扩展日志服务。只有一个实例。
好吧,我说这两个论点都是有效的。同样,这里的问题不在单例模式上。重构是否可行,取决于架构决策和权重。通常,重构是最后需要的纠正措施,这是另一个问题。
评论
@gnat谢谢!我只是想在编辑答案中添加一些有关使用Singletons的警告...您的报价很合适!
– Cregox
13年11月13日在13:30
很高兴您喜欢它。不确定这样做是否有助于避免投票不足-可能读者在这篇文章中提出的问题很难与问题中提出的具体问题联系起来,特别是鉴于先前答案中提供的出色分析
– gna
13年11月13日在13:47
我对这里的总体方向表示同意,尽管对于一个似乎并不比一个极简陋的IoC容器(甚至比Funq更基本的容器)的库来说,它可能有点过于热情。服务定位器实际上是一种反模式;这是一个有用的工具,主要用于旧项目/布朗菲尔德项目,以及“穷人的DI”,因为重构所有东西以正确使用IoC容器会太昂贵。
– Aaronaught
13年11月14日,0:31
@Cawas:自然很难推理和检验全局状态。我很少使用单例。我通常只会创建一个类的单个实例,并通过依赖注入在整个应用程序中使用它,但这是另一回事。真正的单身人士很少见。有些单身人士很丑陋,但很方便-伐木是最好的例子-但我大多认为它们只是过度使用了。
–乔恩·斯基特(Jon Skeet)
2014年1月30日13:11
@Cawas:啊,对不起-与java.awt.Toolkit混淆了。但我的观点是相同的:工具箱听起来像是一堆无关紧要的抓包,而不是一个目的一致的类。听起来对我来说不是很好的设计。 (请注意,您引用的文章来自2001年,之前依赖注入和DI容器已经普及。)
–乔恩·斯基特(Jon Skeet)
2014年1月30日14:54
#6 楼
我的单例设计模式的主要问题在于,很难为您的应用程序编写好的单元测试。每个与该“管理器”相关的组件都通过查询其单例实例来做到这一点。而且,如果您要为此类组件编写单元测试,则必须将数据注入此单例实例,这可能并不容易。
如果另一方面,您的“经理”被注入到通过构造函数参数来依赖组件,并且组件不知道管理器的具体类型,仅不知道管理器实现的接口或抽象基类,那么在测试依赖项时,单元测试可以提供管理器的替代实现。 br />
如果您使用IOC容器来配置和实例化构成应用程序的组件,那么您可以轻松地配置IOC容器以仅创建“管理器”的一个实例,从而实现相同的目的,只有一个实例可以控制全局应用程序缓存。
但是,如果您不关心单元测试,那么单例设计模式就可以了。 (但我还是不会这样做)
评论
很好地解释了,这是最能说明测试Singletons问题的答案
–JoséTomásTocino
16-09-27在9:47
#7 楼
从某种意义上说,任何设计计算都可以是好是坏,从根本上来说,单例并不坏。它只能永远是正确的(给出预期的结果)或不正确。如果使代码更清晰或更高效,它也可能有用或无效。单例有用的一种情况是它们代表一个真正唯一的实体。在大多数环境中,数据库是唯一的,实际上只有一个数据库。连接到该数据库可能很复杂,因为它需要特殊权限或遍历几种连接类型。仅出于这个原因,将连接组织为单例可能很有意义。
但是您还需要确保单例确实是单例,而不是全局变量。当单个唯一数据库实际上是4个数据库,每个数据库分别用于生产,登台,开发和测试装置时,这一点就很重要。 Database Singleton将找出应该连接的数据库实例,获取该数据库的单个实例,如果需要,则将其连接,然后将其返回给调用方。
单例(这是大多数程序员不高兴的时候),它是一个延迟实例化的全局变量,没有机会注入正确的实例。
设计良好的单例模式的另一个有用功能是它通常是不可观察的。呼叫者要求连接。提供该服务的服务可以返回一个池化对象,或者如果它正在执行测试,则可以为每个调用者创建一个新对象,或者提供一个模拟对象。
#8 楼
代表实际对象的单例模式的使用是完全可以接受的。我为iPhone写作,并且在Cocoa Touch框架中有很多单例。应用程序本身由类UIApplication
的单例表示。您只有一个应用程序,因此以单例表示它是适当的。将单例用作数据管理器类是可以的,只要它设计正确即可。如果这是一堆数据属性,那没有比全局范围更好。如果是一组getter和setter,那会更好,但仍然不是很好。如果这是一个真正管理数据所有接口的类,包括获取远程数据,缓存,设置和拆卸……那可能非常有用。
#9 楼
单例只是面向服务的体系结构到程序中的投影。API是协议级别的单例的示例。您可以通过本质上是单例的方式访问Twitter,Google等。那么,为什么单例在程序中变得不好呢?
这取决于您对程序的看法。如果您将程序视为服务社会,而不是随机绑定的缓存实例,那么单例就很有意义。
单子是服务访问点。紧密绑定的功能库的公共接口可能隐藏了非常复杂的内部体系结构。
所以我认为单例与工厂没有什么不同。单例可以传入构造函数参数。它可以由某些上下文创建,例如,该上下文知道如何根据所有可能的选择机制来解析默认打印机。为了进行测试,您可以插入自己的模拟。因此,它可以非常灵活。
该密钥在执行时需要在程序内部,并且需要一些功能,因此我可以完全放心访问单例服务已就绪并可以使用。当在进程中启动必须经过状态机才能被视为就绪的不同线程时,这是关键。
通常,我会包装一个
XxxService
类,该类将单例包装在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
#10 楼
第一个问题,您在应用程序中发现很多错误吗?也许忘记更新缓存,或者缓存不良或发现难以更改? (我记得一个应用程序不会更改大小,除非您也更改了颜色...不过,您可以更改颜色并保留大小)。您要做的是拥有该类,但删除所有静态成员。好的,这不是必要的,但是我推荐。确实,您只是像普通类一样初始化该类,然后将指针通过。不要再说ClassIWant.APtr()。LetMeChange.ANYTHINGATALL()。andhave_no_structure()
它不那么令人困惑。有些地方您现在不应该更改某些内容,因为它不再具有全局性,您现在无法更改。我所有的经理班都是普通班,就那样对待。
#11 楼
IMO,您的例子听起来还不错。我建议分解如下:每个数据对象(以及每个数据对象的后面)的缓存对象;缓存对象和db访问器对象具有相同的接口。这样就可以在代码内外交换缓存。加上它提供了一条简单的扩展路径。图形:
DB
|
DB Accessor for OBJ A
|
Cache for OBJ A
|
OBJ A Client requesting
DB访问器和缓存可以从同一个对象或鸭子类型继承来寻找就像同一个物体只要您可以插入/编译/测试并且它仍然可以工作。
这将使事情解耦,因此您无需添加和修改某些Uber-Cache对象即可添加新的缓存。 YMMV。 IANAL。 ETC。
#12 楼
派对晚了一点,但无论如何。Singleton就像其他任何东西一样,是工具箱中的工具。希望您的工具箱中不仅有一把锤子。
请考虑以下事项: >
public void DoSomething()
{
MySingleton.Instance.Work();
}
第一种情况导致高耦合等;据我所知,第二种方式没有问题@Aaronaught正在描述。有关如何使用它的一切。
评论
不同意。尽管第二种方法是“更好”(减少耦合),但是它仍然存在DI解决的问题。您不应该依赖类的使用者来提供服务的实现-在创建类时最好在构造函数中完成。您的界面只需要参数上的最低要求。您的班级也有一个不错的机会需要操作单个实例-再次,依靠使用者强制执行此规则是有风险且不必要的。
– AlexFoxGill
2013年12月10日16:45
有时,Di对于给定的任务而言是过大的杀伤力。示例代码中的方法可以是构造函数,但不一定是构造函数-无需查看具体示例的模拟参数。另外,DoSomething可以采用ISomething,而MySingleton可以实现该接口-好的,这不是示例,而是示例。
– Evgeni
2013年12月10日17:17
#13 楼
让每个屏幕都使用其构造函数中的Manager。启动应用程序时,您将创建一个管理器实例并将其传递给其他人。配置更改和测试中。此外,您可以并行运行应用程序的多个实例或应用程序的多个部分(适合测试!)。最后,您的经理将死于其拥有的对象(启动类)。
将应用程序像树一样构造,上面的东西拥有它们下面使用的所有东西。不要实现像网格这样的应用程序,每个人都认识每个人,并通过全局方法找到彼此。
评论
使用Singleton应该解决什么问题?与替代方案(例如静态类)相比,解决该问题的方法如何更好?@Anon:使用静态类如何使情况更好。仍然存在紧密耦合吗?
@马丁:我并不是说它会让它“更好”。我建议在大多数情况下,单例是解决问题的解决方案。
@Anon:不对。静态类使您(几乎)无法控制实例化,并使多线程比Singletons更加困难(因为您必须序列化对每个方法的访问,而不仅仅是序列化实例)。单例也至少可以实现静态类不能实现的接口。静态类当然具有它们的优点,但是在这种情况下,Singleton绝对是两个明显弊端中的较小者。实现任何可变状态的静态类就像一个闪烁的霓虹灯“警告:不良的设计!”标志。
@Aaronaught:如果您只是同步对单例的访问,则您的并发性将被破坏。在获取单例对象之后,另一个线程将进入运行状态,并且出现竞争状态,您的线程可能会中断。在大多数情况下,使用Singleton代替静态类只是在拿掉警告标志并思考解决问题的方法。