设计模式(0)简单工厂模式html
源码地址git
单例模式是GOF二十三中经典设计模式的简单经常使用的一种设计模式,单例模式的基本结构需知足如下要求。github
要知足上面的两点要求,应该很容易的想到:设计模式
1.该类的构造函数应该是私有的,不能随意被实例化是保证只有一个实例的前提。安全
2.该类需提供一个公开的且返回值类型为单例类类型的公用方法。多线程
来看一下单例模式的基本结构图:函数
经过上面对单例模式基本定义的了解,单例模式的应用场景也就很明确了。性能
单例模式适用于各类系统中某个类的对象只能存在一个相似场景, 咱们如今回顾一下上一篇简单工厂模式中的大体实现学习
/// <summary> /// 简单工厂类 /// </summary> public class Factory { /// <summary> /// 建立英雄的静态方法 /// </summary> /// <param name="heroName">英雄名称</param> /// <returns></returns> public static IHero CreateHero(string heroName) { switch (heroName) { case "DH": return new DH(); case "WD": return new WD(); case "KOG": return new KOG(); case "POM": return new POM(); default: return null; } } }
/// <summary> /// 恶魔猎手 /// </summary> public class DH : IHero { /// <summary> /// 秀出本身的技能 /// </summary> public void ShowSkills() { Console.WriteLine("我是恶魔猎手,我会法力燃烧、献祭、闪避和变身。"); } }
经过简单工厂模式确实达到了接口隔离的目的,外部使用无需关注内部类的具体实现工程,只经过简单工厂类建立想要的对象便可,但这里有一个致命的问题就是,咱们玩儿游戏的过程当中,英雄会存在一个死亡和复活的场景,咱们简单的把英雄祭坛理解为建立英雄的简单工厂,假设当咱们复活英雄的时候,是经过工厂类建立英雄的一个过程,那么咱们面临的问题就出现了,我原本一个6级的大恶魔猎手,因为走位过分风骚,走进了祭坛,如今在经过工厂建立的时候,因为是又从新new了一个对象,从祭坛中走出了一个萌叉叉的1级小恶魔猎手……测试
为保证个人那个6级大恶魔仍是那个6级大恶魔,一身装备一个很多的走出祭坛,至此也就到了必须引入单例模式的时候了。
按照单例模式的2个基本特征:私有的构造函数和公开的GetInstance方法。将DH类进行以下改造,代码的具体意图已经经过注释详细解释。
/// <summary> /// 恶魔猎手 /// </summary> public class DH : IHero { //定义一个静态的DH类变量 private static DH dh; /// <summary> /// 私有的构造函数,可以保证该类不会在外部被随意实例化,是保证该类只用一个实例的基本前提 /// </summary> private DH() { } /// <summary> /// 定义一个静态的公开的GetInstance方法供外部获得DH类惟一实例是调用 /// </summary> /// <returns></returns> public static DH GetInstance() { //先判断dh是否已经被实例化,若未被实例化,先实例化获得DH类的实例 //保证DH类只被实例化一次 if (dh == null) { dh = new DH(); } return dh; } /// <summary> /// 秀出本身的技能 /// </summary> public void ShowSkills() { Console.WriteLine("我是恶魔猎手,我会法力燃烧、献祭、闪避和变身。"); } }
修改Factory简单工厂类中建立DH实例部分的代码
/// <summary> /// 简单工厂类 /// </summary> public class Factory { /// <summary> /// 建立英雄的静态方法 /// </summary> /// <param name="heroName">英雄名称</param> /// <returns></returns> public static IHero CreateHero(string heroName) { switch (heroName) { case "DH": return DH.GetInstance(); //经过DH类公开的静态GetInstance方法获得DH类的实例 case "WD": return new WD(); case "KOG": return new KOG(); case "POM": return new POM(); default: return null; } } }
客户端测试
static void Main(string[] args) { IHero dh1 = Factory.CreateHero("DH"); IHero dh2 = Factory.CreateHero("DH"); if (dh1.Equals(dh2)) Console.WriteLine("恶魔猎手:我仍是从前的我。"); else Console.WriteLine("恶魔猎手:我已不是从前的我。"); IHero wd1 = Factory.CreateHero("WD"); IHero wd2 = Factory.CreateHero("WD"); if (wd1.Equals(wd1)) Console.WriteLine("守望者:我仍是从前的我。"); else Console.WriteLine("守望者:我已不是从前的我。"); Console.ReadLine(); }
输出结果以下
至此咱们对DH这个类应用了单例模式来确保不管什么时候走出祭坛的都是同一个DH对象,从DH对象被实例化的实际来看,是在被使用的时候才会被建立,这种方式被成为懒汉式单例模式
有一天突发奇想,我建造两个英雄祭坛(两个简单工厂类),用我APM500+的超快手速,同时在两个祭坛里生产同一个英雄,发现我拥有了2个6级大恶魔……(固然了,实际中不会有这个bug存在)
这就是基本懒汉式单例模式要面对的多线程问题,也就是说基本懒汉式单例模式的写法是没法作到线程级别安全的。
问题的关键就在获取DH类实例的GetInstance方法的内部实现中
if (dh == null) { dh = new DH(); } return dh;
简单来讲就是当第一个线程调用判断if(dh==null)为true,已经进入内部经过调用new进行实例化时,另外一个线程也进行了判断,而偏偏此时dh尚未被实例化完成,一样第二个线程也进入if判断语句的内部,进行dh的实例化,因而就出现了2个DH类的实例,从两个祭坛走出来两个大恶魔。
解决这一问题通常有两种方法饿汉式单例和双重检查锁。
饿汉式单例是在系统初始化时自动完成单例类实例的一种方法,而不是等到须要的时候再初始化,也就是说无论之后你会不会用到这个类的对象,我都会给你实例化一个出来,有一种饥饿难耐的感受在里面,故名饿汉式。
/// <summary> /// 饿汉式单例 /// </summary> public class DH : IHero { //系统初始化时已经将DH类进行实例化 private static readonly DH dh = new DH(); /// <summary> /// 私有的构造函数,可以保证该类不会在外部被随意实例化,是保证该类只用一个实例的基本前提 /// </summary> private DH() { } /// <summary> /// 调用时直接返回已经实例化完成的对象 /// </summary> /// <returns></returns> public static DH GetInstance() { return dh; } /// <summary> /// 秀出本身的技能 /// </summary> public void ShowSkills() { Console.WriteLine("我是恶魔猎手,我会法力燃烧、献祭、闪避和变身。"); } }
这种方法简单直接的解决了线程安全问题,可是因为实在初始化时就将单例类进行了实例化,必定程度上形成了各类资源的浪费,违背了延迟加载的设计思想,通常为了解决单例模式线程安全问题,一般使用双重检查锁的方法。
双重检查锁的命名基于单重检查锁方式而来,单重检查锁是在GetInstance实现的时候先行进行锁定,防止别的线程进入,从而解决线程安全问题的。主要代码以下
//定义一个静态只读的用于加锁的辅助对象 private static readonly object lockObject = new object ();
lock (lockObject) { //先判断dh是否已经被实例化,若未被实例化,先实例化获得DH类的实例 //保证DH类只被实例化一次 if (dh == null) { dh = new DH(); } }
return dh;
这种方式每次都要进行lock操做,其实是一种同步方式,这将会在必定程度上影响系统性能的瓶颈和增长了额外的开销。由此衍生出了双重检查锁的方式,简单来讲就是先判断一次dh是否为null,为null时才进行lock操做,不为null就直接返回。
/// <summary> /// 恶魔猎手 /// </summary> public class DH : IHero { //定义一个静态的DH类变量 private static DH dh; //定义一个静态只读的用于加锁的辅助对象 private static readonly object lockObject = new object (); /// <summary> /// 私有的构造函数,可以保证该类不会在外部被随意实例化,是保证该类只用一个实例的基本前提 /// </summary> private DH() { } /// <summary> /// 定义一个静态的公开的GetInstance方法供外部获得DH类惟一实例是调用 /// </summary> /// <returns></returns> public static DH GetInstance() { //先判断dh是否已经被实例化,若未被实例化,先加锁保证线程安全 if (dh == null) { lock (lockObject) { //先判断dh是否已经被实例化,若未被实例化,先实例化获得DH类的实例 //保证DH类只被实例化一次 if (dh == null) { dh = new DH(); } } } return dh; } /// <summary> /// 秀出本身的技能 /// </summary> public void ShowSkills() { Console.WriteLine("我是恶魔猎手,我会法力燃烧、献祭、闪避和变身。"); } }
本次主要基于上一篇的简单工厂模式,延续的学习使用了单例工厂模式确保一个类实例的全局惟一性,过程当中学习了懒汉式、饿汉式、双重检查锁等具体解决方案及演变过程。
设计模式历来不是单打独斗,核心思想是要根据实际须要利用多种模式互相配合来实现代码结构的最优化和健壮性。