记得n年前从VB6开始学编程(那时对面向对象没有太深理解), VB里面有个Module-模块,用来放全局公用的函数,过程,常数,自定义结构,全局变量等等。等能用VB做些常用的软件了,开始转C#了,动手写程序的时候,突然觉得不适应,全局变量放哪里呢?(那时候真是有点菜菜),刚开始是用static。static 函数,static 变量,凡是想要全局用的都扔到一个static类里面。(其实全局常量和工具方法这么用也没错)但慢慢又发现不对了,静态方法无法调用示例方法啊,怎么办呢,傻眼了。
忘了从那里看到了单例模式,如获至宝,这不就是我要的么,简单易用?于是乎,程序中单例到处可见,当成万金油了,也没觉得有什么不好。那时候的单例大概是这么用的:
随着对面向对象认识的深入,越发的觉得单例模式不简单,有人批判,有人离不开,总而言之,没有最好的单例模式,只有最合适的。下面的代码分别演示了我见过的不同形式的用法,加入一点个人意见,算是抛砖引玉了。
(来自Robert C. Martin大牛的“敏捷软件开发”的一个例子)
怎么样,用属性是不是觉某些人觉得更.net。
加了锁,防止多个线程同时第一次访问,初始化的时候带来线程不同步的隐患(如果Singleton构造函数中有不少逻辑的话,线程同步的问题就很明显了)。
还是在Robert的书中看到他这么用,并且提示是利用的了.net的初始化功能。 没用if语句来避免多次创建,我很理解,但是没有锁,会不会有隐患呢?困扰了我好一阵, 直至看了Jeffrey Richter的“CLR via C#”,书中谈到类型构造器,是这么说的,“CLR希望确保在每个AppDomain中,一个类型构造器只执行一次。为了保证这一点,在调用类型构造器时,调用线程要获取一个互斥线程同步锁。这样一来,如果多个线程试图同时调用某个类型的静态构造器,只有一个线程才可以获得锁,其他线程会被阻塞(blocked)。第一个线程会执行静态构造器中的代码。当第一个线程离开构造器后,正在等待的线程将被唤醒,然后发现构造器的代码已经被执行过。因此,这些线程不会再次执行代码,将直接从构造器方法返回。”从而得出结论:“由于CLR保证一个类型构造器在每个AppDomain中只执行一次,而且(这种执行)是线程安全的,所以非常适合在类型构造器中初始化类型需要的任何单实例(Singleton)对象”。
身边的一位哥们总爱这么用,问他为什么,他也不知道。现在理解了,static SingletonInstance中的初始化是线程安全的,但是为啥要放在嵌套类中呢??向下面这样不就得了?
另外,还是从Jeffrey的书中看到上面代码和下面代码是等效的:
C#提供了简单的语法来初始化类型的静态字段,生成上述代码时,编译器自动为Singleton类生成一个类型构造器 static SomeType(){ Instance = new Singleton; },呵呵,代码是不是越来越短了?
该结束了,关于单例模式的话题很深奥,我在Christian Gross的“.Net 2.0模式开发实战中”还看过一种通用的基于泛型的读-写线程安全的可复用的单例,一个程序中所有单例类实现不必丢的到处都是,最终用法类似于
可复用,线程安全,并且单例允许传入参数,一劳永逸,哈哈。代码太长了,就不再引用了,有兴趣的人找书看吧。现在我的代码中,单例的使用越来越谨慎了,有很多好的模式可以用,不要把单例当成万能解决方案,要看到单例的隐患和复杂性,从而选择最合适的用法。