C#设计模式:单件(例)模式 -- 类也玩计划生育
这里写的代码,相当于《Head First 设计模式》的读书笔记,原书是java的,自己在学习的过程中将其翻译为C#:
(一)剖析经典的单件模式实现
单件模式
-- 确保一个类只有一个实例,并提供一个全局访问点
-- 单件模式的类图可以说是所有模式的类图中最简单的
-- 有一些对象其实我们只需一个,如线程池、缓存、对话框、处理偏好设置和注册表的对象、日志对象和充当打印机、显卡等设备的驱动程序的对象等。如果制造出多个实例,可能导致许多问题,如程序的行为异常、资源使用过度,或者结果不一致等
1.新建一个控制台应用程序:SingletonPatternDemo。
2.新建一个类:Singleton.cs
1 namespace SingletonPatternDemo 2 { 3 public class Singleton 4 { 5 /// <summary> 6 /// 利用一个静态变量来记录Singleton类的唯一实例 7 /// </summary> 8 private static Singleton _uniqueInstance; 9 10 //这里是其它变量... 11 12 /// <summary> 13 /// 构造器私有化:只能在类内部才能调用构造器 14 /// </summary> 15 private Singleton() { } 16 17 /// <summary> 18 /// 只能通过该方法获取到对象实例 19 /// </summary> 20 /// <returns></returns> 21 public static Singleton GetInstance() 22 { 23 //【注意】如果我们不需要该实例,它就永远不会产生。这就是“延迟实例化”(lazy instantiaze) 24 return _uniqueInstance ?? (_uniqueInstance = new Singleton()); 25 26 #region 上行相当于以下代码 27 //if (_uniqueInstance == null) 28 //{ 29 // _uniqueInstance = new Singleton(); 30 //} 31 32 //return _uniqueInstance; 33 #endregion 34 } 35 36 //这里是其它方法... 37 } 38 }
下面我们去掉注释看看
1 namespace SingletonPatternDemo 2 { 3 public class Singleton 4 { 5 private static Singleton _uniqueInstance; 6 7 private Singleton() { } 8 9 public static Singleton GetInstance() 10 { 11 return _uniqueInstance ?? (_uniqueInstance = new Singleton()); 12 } 13 } 14 }
哇塞,这么简单啊!如果你也这么认为的话,那就错啦......接下来,我们看下第(二)部分
(二)场景应用
巧克力工厂
现代化的巧克力工厂具备计算机控制的巧克力锅炉,锅炉做的事,就是把巧克力和牛奶融在一起,然后送到下一个阶段,以制造成巧克力棒。
这里有一个Choc-O-Holic公司的工业强度巧克力锅炉控制器,用于控制锅炉的日常运作,比如:锅炉内为空时才可以加入原料、锅炉内存在原料并且尚未煮沸时才能够进行煮沸,还有排出牛奶和巧克力的混合物时要求炉内存在已经煮沸的原料等。
下列是巧克力锅炉控制器的代码:
1 namespace SingletonPatternDemo 2 { 3 /// <summary> 4 /// 巧克力锅炉 5 /// </summary> 6 public class ChocolateBoiler 7 { 8 private bool Empty { get; set; } 9 private bool Boiled { get; set; } 10 11 //代码开始时,锅炉为空,未燃烧 12 public ChocolateBoiler() 13 { 14 Empty = true; 15 Boiled = false; 16 } 17 18 /// <summary> 19 /// 填充 20 /// </summary> 21 public void Fill() 22 { 23 //在锅炉内填入原料时,锅炉必须为空; 24 //填入原料后就把两个属性标识好 25 if (Empty) 26 { 27 //在锅炉内填满巧克力和牛奶的混合物... 28 29 Empty = false; 30 Boiled = false; 31 } 32 } 33 34 /// <summary> 35 /// 排出 36 /// </summary> 37 public void Drain() 38 { 39 //锅炉排出时,必须是满的,并且是煮过的; 40 //排出完毕后将Empty标志为true。 41 if (!Empty && Boiled) 42 { 43 //排出煮沸的巧克力和牛奶... 44 45 Empty = true; 46 } 47 } 48 49 /// <summary> 50 /// 煮沸 51 /// </summary> 52 public void Boil() 53 { 54 //煮混合物时,锅炉必须是满的,并且是没有煮过的; 55 //煮沸后,就把Boiled标识为true。 56 if (!Empty && !Boiled) 57 { 58 //将炉内物煮沸... 59 60 Boiled = true; 61 } 62 } 63 } 64 }
试试根据(一)中所学的内容将它修改成单例模式
1 namespace SingletonPatternDemo 2 { 3 /// <summary> 4 /// 巧克力锅炉 5 /// </summary> 6 public class ChocolateBoiler 7 { 8 private static ChocolateBoiler _uniqueInstance; //【新增】一个静态变量 9 10 private bool Empty { get; set; } 11 private bool Boiled { get; set; } 12 13 //代码开始时,锅炉为空,未燃烧 14 private ChocolateBoiler() //【修改】原来是public 15 { 16 Empty = true; 17 Boiled = false; 18 } 19 20 /// <summary> 21 /// 获取ChocolateBoiler对象实例 22 /// </summary> 23 /// <returns></returns> 24 public static ChocolateBoiler GetInstance() //【新增】一个静态方法 25 { 26 return _uniqueInstance ?? (_uniqueInstance = new ChocolateBoiler()); 27 } 28 29 /// <summary> 30 /// 填充 31 /// </summary> 32 public void Fill() 33 { 34 //在锅炉内填入原料时,锅炉必须为空; 35 //填入原料后就把两个属性标识好 36 if (Empty) 37 { 38 //在锅炉内填满巧克力和牛奶的混合物... 39 40 Empty = false; 41 Boiled = false; 42 } 43 } 44 45 /// <summary> 46 /// 排出 47 /// </summary> 48 public void Drain() 49 { 50 //锅炉排出时,必须是满的,并且是煮过的; 51 //排出完毕后将Empty标志为true。 52 if (!Empty && Boiled) 53 { 54 //排出煮沸的巧克力和牛奶... 55 56 Empty = true; 57 } 58 } 59 60 /// <summary> 61 /// 煮沸 62 /// </summary> 63 public void Boil() 64 { 65 //煮混合物时,锅炉必须是满的,并且是没有煮过的; 66 //煮沸后,就把Boiled标识为true。 67 if (!Empty && !Boiled) 68 { 69 //将炉内物煮沸... 70 71 Boiled = true; 72 } 73 } 74 } 75 }
【问题】万一同时存在多个ChocolateBoiler(巧克力锅炉),可能将发生很多糟糕的事情!... 敬请收看第(三)部分
(三)处理多线程
现在,只要使用lock,就可以很简单地解决(二)中出现的问题了
1 namespace SingletonPatternDemo 2 { 3 public class Singleton 4 { 5 /// <summary> 6 /// 利用一个静态变量来记录Singleton类的唯一实例 7 /// </summary> 8 private static Singleton _uniqueInstance; 9 10 private static readonly object Locker = new object(); 11 12 //这里是其它变量... 13 14 /// <summary> 15 /// 构造器私有化:只能在类内部才能调用构造器 16 /// </summary> 17 private Singleton() { } 18 19 /// <summary> 20 /// 只能通过该方法获取到对象实例 21 /// </summary> 22 /// <returns></returns> 23 public static Singleton GetInstance() 24 { 25 //lock:迫使每个线程在进入该方法之前,需要等候别的线程离开该方法, 26 // 也就是说,不会有两个线程可以同时进入该方法 27 lock (Locker) 28 { 29 if (_uniqueInstance == null) 30 { 31 //【注意】如果我们不需要该实例,它就永远不会产生。这就是“延迟实例化”(lazy instantiaze) 32 _uniqueInstance = new Singleton(); 33 } 34 } 35 36 37 return _uniqueInstance; 38 39 } 40 41 //这里是其它方法... 42 } 43 }
但是,现在又出现了性能的问题!...
方案一:使用“急切”创建实例,而不用延迟实例化的做法
1 namespace SingletonPatternDemo 2 { 3 public class Singleton 4 { 5 //如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,可以选择这种方法 6 7 //在静态初始化器中创建单件,这段代码保证了线程安全 8 private static readonly Singleton UniqueInstance = new Singleton(); 9 10 private Singleton() { } 11 12 public static Singleton GetInstance() 13 { 14 return UniqueInstance; 15 } 16 } 17 }
方案二:用“双重检查加锁”
1 namespace SingletonPatternDemo 2 { 3 public class Singleton 4 { 5 private static Singleton _uniqueInstance; 6 private static readonly object Locker = new object(); 7 8 private Singleton() { } 9 10 public static Singleton GetInstance() 11 { 12 //检查实例,如果不存在,就进入同步区块 13 if (_uniqueInstance == null) 14 { 15 lock (Locker) 16 { 17 if (_uniqueInstance == null) 18 { 19 //只有第一次才彻底执行这里的代码 20 _uniqueInstance = new Singleton(); 21 } 22 } 23 } 24 25 return _uniqueInstance; 26 } 27 28 } 29 }
完毕... ...