通俗易懂设计模式解析——单例模式
一、前言
在上一节中我们对设计模式进行了一定的介绍及分类。设计模式分为创建型、结构型、行为型。创建型模式——主要负责对象的创建。结构型职责——主要负责处理类与对象的组合。行为型模式——主要负责类与对象交互中的职责的分配问题。今天我们也是讲述介绍创建型模式中的第一个模式——单例模式。
二、单例模式介绍
(一)来由
单例模式(Singleton Pattern)是最简单的一个设计模式 ,这种设计模式属于创建型模式。在程序中总会有一些特殊的类。它们必须保证在系统中只存在一个实例,这个单一的类自己创建自己的对象,同时确保只有单个对象被创建,并且提供唯一的访问形式。可以直接进行访问,不用再新建实例。
那么如何避开常规的设计,来实现一个类一个实例、并且保证唯一调用呢?这时候就是单例模式施展身手的时候了。
(二)意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
(三)单例模式实现方法
单例模式到底又是如何实现的呢?既然是单一实例,那么队友多线程又该如何处理呢?下面我们一一来看看单例模式的实现。单例模式我们又涉及到其实现的多种形式——非线程安全、线程安全、双重验证线程安全、不用锁线程安全、完全延迟加载、使用.NET4的Lazy<T>类型。
1. 非线程安全
/// <summary> /// 非线程安全 /// </summary> public sealed class Singleton1 { /// <summary> /// 定义静态变量保存实例 /// </summary> public static Singleton1 Instance = null; /// <summary> /// 定义私有构造函数保护,使其他地方不得实例 /// </summary> private Singleton1() { } public string GetString() { return "非线程安全的单例模式"; } /// <summary> /// 定义公共方法,实现全局访问 /// </summary> /// <returns></returns> public static Singleton1 GetInstance() { //判断实例状态 if (Instance==null) { Instance = new Singleton1(); } return Instance; } }
在上述事例中完美的实现了单线程的单例模式的情况。这里我们也需要注意一些的情况:
① 单例类包含一个private的私有构造函数
② 类申明sealed 密封不可继承(不强制)
③ 类中有一个静态变量保存实例
④ 类中提供有一个静态方法或者属性实现实例的创建引用全局调用访问
⑤ 在多线程中单例模式需要另行处理,不然有可能得到类的多个实例
2. 线程安全
/// <summary> /// 线程安全单例模式 /// </summary> public sealed class Singleton2 { /// <summary> /// 定义静态变量保存实例 /// </summary> public static Singleton2 Instance = null; private static readonly object locks=new object(); /// <summary> /// 定义私有构造函数保护,使其他地方不得实例 /// </summary> private Singleton2() { } public string GetString() { return "线程安全的单例模式"; } /// <summary> /// 定义公共方法,实现全局访问 /// </summary> /// <returns></returns> public static Singleton2 GetInstance() { //对线程进行加锁限制,挂起后来的线程。保证实例安全 lock (locks) { if (Instance == null) { Instance = new Singleton2(); } } return Instance; } }
3. 不用锁线程安全
/// <summary> /// 不用锁线程安全单例模式 /// </summary> public sealed class Singleton3 { /// <summary> /// 定义静态变量保存实例 /// </summary> private static readonly Singleton3 Instance = new Singleton3 (); static Singleton3() { } /// <summary> /// 定义私有构造函数保护,使其他地方不得实例 /// </summary> private Singleton3() { } public string GetString() { return "不用锁线程安全单例模式"; } /// <summary> /// 定义公共方法,实现全局访问 /// </summary> /// <returns></returns> public static Singleton3 GetInstance() { return Instance; } }
这个实现方法没有使用到锁,但是也实现了线程安全。在第一次调用的时候会创建一个instance。这个实现也有一定的安全隐患。
- instance被创建的时机不明,任何对Singleton的调用都会提前创建instance
- static构造函数的循环调用。如有A,B两个类,A的静态构造函数中调用了B,而B的静态构造函数中又调用了A,这两个就会形成一个循环调用,严重的会导致程序崩溃。
- 我们需要手动添加Singleton的静态构造函数来确保Singleton类型不会被自动加上beforefieldinit这个Attribute,以此来确保instance会在第一次调用Singleton时才被创建。
- readonly的属性无法在运行时改变,如果我们需要在程序运行时dispose这个instance再重新创建一个新的instance,这种实现方法就无法满足。
4. 完全延迟加载
/// <summary> /// 实现完全延迟加载单例模式 /// </summary> public sealed class Singleton4 { /// <summary> /// 定义私有构造函数保护,使其他地方不得实例 /// </summary> private Singleton4() { } /// <summary> /// 提供访问位置 /// </summary> public static Singleton4 Instance { get { return GetInstance.instance; } } /// <summary> /// 定义私有类确保第一次加载是初始化及调用 /// </summary> private class GetInstance { static GetInstance(){} internal static readonly Singleton4 instance = new Singleton4(); } public string GetString() { return "实现完全延迟加载单例模式"; } }
它确保了instance只会在Instance的get方法里面调用,且只会在第一次调用前初始化。是上一个版本的延迟加载的版本
5. 使用.NET4的Lazy<T>类型
/// 使用Lazy<T>实现完全延迟加载单例模式 /// </summary> public sealed class Singleton5 { /// <summary> /// 延迟加载初始化 /// </summary> private static readonly Lazy<Singleton5> lazy=new Lazy<Singleton5>(()=>new Singleton5()); /// <summary> /// 定义私有构造函数保护,使其他地方不得实例 /// </summary> private Singleton5() { } /// <summary> /// 提供全局访问点 /// </summary> /// <returns></returns> public static Singleton5 Instance() { return lazy.Value; } public string GetString() { return "实现完全延迟加载单例模式"; } }
在.NET4.0中,可以使用Lazy<T> 来实现对象的延迟初始化,从而优化系统的性能。延迟初始化就是将对象的初始化延迟到第一次使用该对象时。延迟初始化是我们在写程序时经常会遇到的情形,例如创建某一对象时需要花费很大的开销,而这一对象在系统的运行过程中不一定会用到,这时就可以使用延迟初始化,在第一次使用该对象时再对其进行初始化,如果没有用到则不需要进行初始化,这样的话,使用延迟初始化就提高程序的效率,从而使程序占用更少的内存。
三、使用场合及优缺点
一、使用场合
1、当类只需要且只能有一个实例并且需要全局访问的时候。
2、当类是使用子类化扩展,并且无需更改代码就可以使用扩展实例的情况下。
二、优点
1、控制实例数量:保证实例数量的唯一且是全局访问。
2、灵活性:类控制了实例化的全过程,这样可以更加灵活的修改实例化过程
3、资源节省:避免对资源的多重占用
三、缺点
1、没有接口、也不能继承。这个与单一责任原则相冲突,一个类只应该负责其逻辑,而不应该去负责如何实例。
四、总结
在设计模式的学习过程中,单例模式较为简单,实现操作并不是特别难,但是在我们实例运用中也当注意下,比较如果使用出现问题。找到问题还是稍微困难的。这篇文章也介绍了几种单例模式的使用方法,在我们使用时择优选取最佳方案。下一节我们将为全面讲述二级野怪、并学习攻克它。