设计模式系列-单例模式
今天单位有自己的食堂啦,发邮件收了工卡之后统一拿去激活,以后就用工卡去食堂吃饭啦,早上2元,中午10元,晚上3元,都是自助噢,很爽,不过还是有一推人没有第一时间激活卡,也有的人激活卡了忘记自己激活了,我就是其中一个,无奈下我只好到食堂自己去激活卡了,餐厅只有一个机会卡的柜台所以激活的时候需要排队,还好我来的早,提前搞定,刚出门时就看又一批激活工卡的人来啦。
下班后。不例外继续到食堂吃饭,便宜嘛自然人也就不少,当然也包括激活工卡的人,吃饭是心里就暗自想,这不就是设计模式里面的一种场景吗? 一个餐厅只有一个激活卡的柜台。在餐厅的作用域内提供了一个全局的访问点。
1.模拟该场景:
首先分析一个这个场景,首先:
① 我们需要一个餐厅类(Restaurant)。
② 接下来我们还要设计一个柜台类(Counter),餐厅包含柜台,并且只能拥有一个柜台。
③ 柜台类中包含一个用来激活工卡(ActivationCard)的功能,好让员工可以在餐厅用餐。
废话不多说模拟场景代码如下:
public class Restaurant
{
//餐厅包含柜台
public static Counter counter = new Counter();
}
//柜台类
public class Counter
{
//激活工卡方法
public void ActivationCard()
{
//激活工卡的过程
Console.WriteLine("工卡激活成功!");
}
}
static void Main(string[] args)
{
//此时前往餐厅办理激活工卡
Counter counter = Restaurant.counter;
counter.ActivationCard();
}
这时,似乎不出来代码中有什么问题,但是仔细想想,如果这个时候我在调用激活工卡的时候将获取到的柜台对象重新实例化一次呢?仿佛凭空多制造出了一个柜台的感觉一样,为了确保我们在同一个范围内只有一个柜台实例我们可以考虑单例模式。
2.引入单例模式
那么如何让同一个范围内的对象仅有一个实例呢?
我们可以类的定义中做手脚,改装后的柜台类如下:
public class Counter
{
//定义一个私有的静态柜台
private static Counter instance;
//私有构造方法,禁止外部实例化该类型
private Counter()
{
}
public static Counter GetCounterInstance()
{
if (instance == null)
{
//类型内部可以实例化私有构造实例
instance = new Counter();
}
return instance;
}
//激活工卡方法
public void ActivationCard()
{
//激活工卡的过程
Console.WriteLine("工卡激活成功!");
}
}
那么柜台是属于餐厅的,必然要给餐厅中安装柜台,平且让餐厅提供激活工卡的服务。餐厅类代码如下:
public class Restaurant
{
//激活工卡服务,返回柜台对象(对应显示现实中,指向柜台的位置)
public Counter ActivationCardService()
{
return Counter.GetCounterInstance();
}
}
主函数调用代码如下:
{
//餐厅实例
Restaurant res = new Restaurant();
//进入餐厅找到柜台,激活工卡
res.ActivationCardService().ActivationCard();
}
这时一个最基本的单例模式就体现出来了,前面介绍过,单例模式:就是保证一个类仅有一个实例,并提供一个访问它的全局访问点。
现在看来上面的代码确实做到了这一点,但是在多线程情况下会怎样呢?我们来看获得餐厅实例的方法,这个方法里面实现了仅创建一个实例的逻辑,代码如下:
{
if (instance == null)
{
//类型内部可以实例化私有构造实例
instance = new Counter();
}
return instance;
}
如果这是两个线程同时访问,那么就会造成同时创建了两个实例,因为两个线程到达这段代码时,该实例都是未创建的,那么如何解决这个问题呢?在实例化方法中加入一个临界区是一个很好的解决办法,这样就能保证一个线程进去临界区时,另一个线程在临界区外面等待,代码如下:
public static Counter GetCounterInstance()
{
//加入临界区 当一个线程进入,后面线程等待
lock (obj)
{
if (instance == null)
{
//类型内部可以实例化私有构造实例
instance = new Counter();
}
}
return instance;
}
这个时候就解决了线程同步的问题了,在多线程环境下也会只有一个实例被创建,成为了真真正正的单例模式!
那么在考虑一个问题,回到我们之前的激活工卡场景中,排队的人中有很多卡已经激活自己却不知道的,也就是说,当很多线程执行创建实例的代码时,实例已经是创建过的,那么不必要实例化的线程我们能不能就不让它们进入临界区,减少系统的性能开销?
代码如下:
public static Counter GetCounterInstance()
{
//当实例未被实例化在进入临界区
if (instance == null)
{
//加入临界区 当一个线程进入,后面线程等待
lock (obj)
{
if (instance == null)
{
//类型内部可以实例化私有构造实例
instance = new Counter();
}
}
}
return instance;
}
这下就是我们传说中的单例模式了,并且对性能也进行了优化。
3.C#版的单例模式
那么根据C#的语言特性,实现单例模式又是什么样子呢?
①我们可以吧GetCounterInstance()方法 替换成 C#的属性。
②我们可以使用密封类,阻止类型的派生,避免多实例化对象。
③使用静态并且只读的私有字段存放类型的唯一实例
代码如下:
public sealed class Counter
{
//在第一次调用类成员时,初始化唯一实例
private static readonly Counter instance = new Counter();
private Counter()
{
}
//返回类型实例属性
public static Counter Instance
{
get
{
return instance;
}
}
//激活工卡方法
public void ActivationCard()
{
//激活工卡的过程
Console.WriteLine("工卡激活成功!");
}
}
晕。当我想完时,晚饭已经凉了。算了,这不就是程序员的生活嘛?