细颗粒度Singleton模式实现

背景讨论

作为一个很典型的设计模式,Singleton模式常常被用来展示设计模式的技巧,并且随着技术的演进,.NET语言和Java都已经把经典《Design Patterns : Elements of Reusable Object-Oriented Software》中所定义的Singleton模式作了完善,例如C#可以通过这样一个非常精简但又很完美的方式实现了一个进程内部线程安全的 Singleton模式。

C# 最经典Singleton模式的实现(Lazy构造方式)

C# 通过Double Check实现的相对线程安全的Singleton模式

C#充分依靠语言特性实现的间接版Singleton模式

 

但项目中我们往往需要更粗或者更细颗粒度的Singleton,比如某个线程是长时间运行的后台任务,它本身存在很多模块和中间处理,但每个线程都希望有自己的线程内单独Singleton对象,其他线程也独立操作自己的线程内Singleton,所谓的线程级Singleton其实他的实例总数 = 1(每个线程内部唯一的一个) * N (线程数)= N。

.NET程序可以通过把静态成员标示为System. ThreadStaticAttribute就可以确保它指示静态字段的值对于每个线程都是唯一的。但这对于Windows Form程序很有效,对于Web Form、ASP.NET Web Service等Web类应用不适用,因为他们是在同一个IIS线程下分割的执行区域,客户端调用时传递的对象是在HttpContext中共享的,也就是说它本身不可以简单地通过System. ThreadStaticAttribute实现。不仅如此,使用System. ThreadStaticAttribute也不能很潇洒的套用前面的内容写成:

因为按照.NET的设计要求不要为标记为它的字段指定初始值,因为这样的初始化只会发生一次,因此在类构造函数执行时只会影响一个线程。在不指定初始值的情况下,如果它是值类型,可依赖初始化为其默认值的字段,如果它是引用类型,则可依赖初始化为null。也就是说多线程情况下,除了第一个实例外,其他线程虽然也期望通过这个方式获得唯一实例,但其实获得就是一个null,不能用。

解决Windows Form下的细颗粒度Singleton问题

对于Windows Forms下的情况,可以通过System. ThreadStaticAttribute比较容易的高速CLR其中的静态唯一属性Instance仅在本线程内部静态,但麻烦的是怎么构造它,正如上面背景介绍部分所说,不能把它放到整个类的静态构造函数里,也不能直接初始化,那么怎么办?还好,那个很cool的实现这里不适用的话,我们就退回到最经典的那个lazy方式加载Singleton实例的方法。你可能觉得,这线程不安全了吧?那种实现方式确实不是线程安全,但我们这里的Singleton 构造本身就已经运行在一个线程里面了,用那种不安全的方式在线程内部实现只有自己"一亩三分地"范围内Singleton的对象反而安全了。新的实现如下:

Unit Test

下面我们分析一下单元测试代码说明的问题:

* 在Work.Procedure()方法中,两次调用到了Singleton类的Instance静态属性,经过验证是同一个Singleton类实例。同时由于Singleton类的构造函数定义为私有,所以线程(客户程序)无法自己实例化Singleton类,因此同时满足该模式的设计意图;
* 通过对每个线程内部使用的Singleton实例登记并检查,确认不同线程内部其实掌握的是不同实例的引用,因此满足我们需要实现的细颗粒度(线程级)的意图;
* 解决Web Form下细颗粒度Singleton问题。

上面用ThreadStatic虽然解决了Windows Form的问题,但对于Web Form应用而言并不适用,原因是Web Form应用中每个会话的本地全局区域不是线程,而是自己的HttpContext,因此相应的Singleton实例也应该保存在这个位置。实现上我们只需要做少许的修改,就可以完成一个Web Form下的细颗粒度Singleton设计:

注:这里的Web Form应用包括ASP.NET Application、ASP.NET Web Service、ASP.NET AJAX等相关应用。但示例并没有在.NET Compact Framework和.NET Micro Framework的环境下进行过验证。

Unit Test


同上,这段单元测试验证了Web Form下的细颗粒度Singleton,通过将唯一实例的存储位置从当前线程迁移到HttpContext,一样可以实现细颗粒度的Singleton设计意图。
更通用的细颗粒度Singleton

但如果你是一个公共库或者是公共平台的设计者,您很难预料到自己的类库会运行在Windows Form还是Web Form环境下,但Singleton模式作为很多公共机制,最常用的包括技术器、时钟等等又常常会成为其他类库的基础,尤其当涉及到业务领域逻辑的时候,很难在开发过程就约定死运行的模式。怎么办?

这里借助一个工具类,通过它判断当前执行环境是Web Form还是Windows Form,然后作一个2 in 1的细颗粒度Singleton(,听起来有点象早年的任天堂游戏卡),不过就像我们提到的面向对象设计的单一职责原则一样,把两个和在一起会产生一些比较难看的冗余代码,但Singleton与其他设计模式有个很显著的区别--他不太希望被外部机制实例化,因为他要保持实例的唯一性,因此一些常用的依赖倒置技巧在这里又显得不太适用。这里实现一个稍有些冗余的Web Form + Windows Form 2 in 1的细颗粒度Singleton如下:

细颗粒度Singleton模式实现

C# 工具类GenericContext

C# 2in 1的细颗粒度Singleton模式实现

小结

设计模式中很多意图部分表述的要求其实也都是有语意范围 的,比如说“唯一”、“所有相关”、“一系列相互依赖的”等,但项目中往往有自己定制化的要求,可能的话建议尽量用语言、语言运行环境的特性完成这些工作。

注:转载自:http://www.infoq.com/cn/articles/fine-grained-singleton-pattern

你可能感兴趣的:(Singleton)