单例模式 是常见的一种开发模式,如何在编程中实现唯一实例呢?
要想实现系统中只存在一个对应的实例,需要两个要素。
①创建实例需要调用构造方法,保证构造方法是私有的。
这样就限制了在其他类中创建该类的对象的情况。即使该类不是密封类,
继承该类的子类中也无法创建父类对象和子类对象,因为在创建的时候需要
调用父类的构造方法,而私有的父类构造方法是子类调用不到的。
②静态方法或属性或只读字段返回该对象类型
既然在其他类中无法创建新的实例,唯一的实例只能在本类的定义中提供了。
这就需要一个静态的成员来保证在其他类中能获得该实例,否则就瞎菜了。
最简单的形式如:
class God { /// <summary> /// 私有构造函数 /// </summary> private God() { } /// <summary> /// 通过静态属性 /// </summary> public static God OnlyGod { //类内部可以调用构造方法 get { return new God(); } } /// <summary> /// 或者通过方法返回 /// </summary> /// <returns></returns> public static God ReturnonlyGod() { return new God(); } /// <summary> /// 或者通过只读字段 /// </summary> public static readonly God ReturnonlyGod = new God(); }
对于如果单例模式创建时需要参数的话,可以选择通过静态方法返回。
需要注意的是:这三种方法、属性或字段不能共存,否则就不是就单例实例了,而是双例模式、三例模式了。如下:
Console.WriteLine(ReferenceEquals(God.OnlyGod,God.ReturnonlyGod())); //False
当然,这不是最标准的。一般的话,是需要定义一个私有的静态变量来作为单例的引用,所以常见的为
public class God { /// <summary> /// 私有静态变量 /// </summary> private static God _onlygod = null; /// <summary> /// 私有构造函数 /// </summary> private God() { } /// <summary> /// 静态属性 /// </summary> public static God OnlyGod { //_onlygod为空,则_onlygod = new God(),并返回 //否则返回不为空的_onlygod get { return _onlygod ??(_onlygod = new God()); } } }
也可以在私有的静态变量定义时直接new God()初始化。为了遵循缓式加载,还是用上面这个比较好。
为了保证单例模式的实现,需要主要的几点:不要继承ICloneable(),不要支持序列化,不能对应多线程环境。
说:单例模式不能滥用,避免安全性问题,主要是考虑到多线程情况,多线程可能得到God类的多个实例
,就是说在平行宇宙中,世界上没有两片相同的叶子是不一定的。
所以要对单例模式进行一定的修订
通过 辅助变量+Double_Lock+Volatile
public class God { /// <summary> /// 私有静态变量+volatile(保证编译顺序) /// </summary> private static volatile God _onlygod = null; /// <summary> /// +辅助对象 /// </summary> private static object LockHelper = new object(); /// <summary> /// 私有构造函数 /// </summary> private God() { } /// <summary> /// 静态属性 /// </summary> public static God OnlyGod { get { //+双检查 if (_onlygod == null) { lock (LockHelper) { if (_onlygod == null) { _onlygod = new God(); } } } return _onlygod; } } }
摘抄总结:单例模式设计的核心是为了控制用户使用new对一个类的实例构造器的任意调用。
下面链接到,方便理解对于静态成员,加载的时候会初始化成员
新链接
静态构造函数相比静态初始化器相比,其优点是可以捕获异常,因为静态构造函数和静态初始化器出错时,CLR将会终止你的程序,而可以捕获异常的静态构造函数就可以使程序继续运行,但其缺点也在此,静态构造函数只能初始化一次,若出现错误造成某个类型没有初始化,那么CLR将不会再次进行初始化尝试。