首先一个问题,什么是单例模式?根据我的理解,单例模式重点突出一个“单”字,无论干什么都是在同一个类中进行的,一个类只能生成一个对象,所有的操作都通过这个对象来进行操作。
很显然,要控制一个类只能提供一个单一的对象供外部调用使用,就需要将构造行数私有化,让其他人(类)不能创建我这个类。同时,要对外提供服务,我就需要将这个已经由我创建好了的对象公布出去,让别的人(类)通过我公布的实例来对我进行访问。
以一个Log类作为例子,在项目开发的生命周期中,需要在无数的地方打印日志。不可能每次调用一下日志打印就要去创建一个日志打印对象,这样多浪费性能。而通过单例模式创建日志对象后,每次访问日志打印方法时,获取创建好了的单例即可。
整个日志类代码如下:
public class Log
{
public static Log _logger = null;
/// <summary>
/// 私有化构造函数
/// </summary>
private Log()
{
}
/// <summary>
/// 创建单例时的锁
/// </summary>
private static readonly object locker = new object();
/// <summary>
/// 提供对外的访问
/// 懒汉式创建方式
/// </summary>
public static Log Instance
{
get
{
if(_logger == null)
{
lock (locker)
{
if(_logger == null)
{
_logger = new Log();
}
}
}
return _logger;
}
}
/// <summary>
/// 写日志的方法
/// </summary>
/// <param name="text"></param>
public void Write(string text)
{
if (string.IsNullOrEmpty(text))
{
return;
}
Console.WriteLine($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}]{text}");
}
}
写日志时,通过调用Write方法,打印日志即可:
static void Main(string[] args)
{
Log.Instance.Write("这个是要打印的Log.");
Console.ReadKey(false);
}
运行结果:
分析代码可以看到,在创建单例时,用了lock来保证线程安全。为什么要这样做呢?来看下下面这段非线程安全创建单例的代码:
public static Log Instance
{
get
{
if (_logger == null)
{
_logger = new Log();
}
return _logger;
}
}
当我们将这段代码应用在多线程高并发环境中时,如果两个线程同时调用Instance后,就能出现创建创建两次对象的情况(每个线程创建一遍)。为了避免出现这种不可预料的错误,就需要在创建单例对象的时候进行加锁,保证从始至终都是一个对象来提供服务。
那么单例模式有什么优点和缺点呢?
优点:
· 单例模式仅在内存中创建了一个实例,减小内存开支。当遇到一个对象需要频繁的进行创建和销毁,而创建和销毁时的性能又不能得到保障时,就可以通过创建单例来避免这种性能损失。
· 避免对资源的多重占用,例如以上的日志Demo,如果要写日志到文件的话,就可以避免对日志文件的多重占用(日志类仅作为一个demo)
缺点:
· 单例模式扩展非常困难,想要扩展,可能只有通过修改代码来进行扩展了。
· 单例模式与单一职责原则冲突,单例模式中一个类要进行的操作肯定不止一种。
分析完优缺点,再更具单例模式特性,很容易就能想到在哪里场景下用单例模式比较合适。比如:
· 生成唯一序列号的场景,单例模式可以保证生成的UUID是完全独立的。
· 需要优化重复IO读写的场景。
· 各种工具类(实际上我觉得各种工具类适合使用扩展方法)
题外话
其实单例也可以不单例,一个单例模式中虽然提供服务的是一个实例,但是一个实例的内部也可以多个对象的组合呀??!
本篇代码下载:点此下载
参考:
1、《设计模式之禅》 秦小波 著
文章评论