设计模式(1)单例模式(Singleton)
0 单例模式简介
0.0 单例模式定义
单例模式是GOF二十三中经典设计模式的简单常用的一种设计模式,单例模式的基本结构需满足以下要求。
- 单例模式的核心结构只有一个单例类,单例模式要保证这个类在运行期间只能被实例化一次,即只会被创建唯一的一个单例类的实例。
- 单例模式需要提供一个全局唯一能得到这个类实例的访问点,一般通过定义一个名称类似为GetInstance的公用方法实现这一目的。
要满足上面的两点要求,应该很容易的想到:
1.该类的构造函数应该是私有的,不能随意被实例化是保证只有一个实例的前提。
2.该类需提供一个公开的且返回值类型为单例类类型的公用方法。
来看一下单例模式的基本结构图:

0.1 单例模式应用场景
通过上面对单例模式基本定义的了解,单例模式的应用场景也就很明确了。
单例模式适用于各种系统中某个类的对象只能存在一个类似场景, 我们现在回顾一下上一篇简单工厂模式中的大致实现
/// <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级大恶魔,一身装备一个不少的走出祭坛,至此也就到了必须引入单例模式的时候了。
1 单例模式详解
1.0单例模式的基本实现-懒汉式单例模式
按照单例模式的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类的实例,从两个祭坛走出来两个大恶魔。
解决这一问题一般有两种方法饿汉式单例和双重检查锁。
1.1 饿汉式单例
饿汉式单例是在系统初始化时自动完成单例类实例的一种方法,而不是等到需要的时候再初始化,也就是说不管以后你会不会用到这个类的对象,我都会给你实例化一个出来,有一种饥饿难耐的感觉在里面,故名饿汉式。
/// <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("我是恶魔猎手,我会法力燃烧、献祭、闪避和变身。");
}
}
这种方法简单直接的解决了线程安全问题,但是由于实在初始化时就将单例类进行了实例化,一定程度上造成了各种资源的浪费,违背了延迟加载的设计思想,一般为了解决单例模式线程安全问题,通常使用双重检查锁的方法。
1.2 双重检查锁
双重检查锁的命名基于单重检查锁方式而来,单重检查锁是在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("我是恶魔猎手,我会法力燃烧、献祭、闪避和变身。");
}
}
2 总结
本次主要基于上一篇的简单工厂模式,延续的学习使用了单例工厂模式确保一个类实例的全局唯一性,过程中学习了懒汉式、饿汉式、双重检查锁等具体解决方案及演变过程。
设计模式从来不是单打独斗,核心思想是要根据实际需要利用多种模式互相配合来实现代码结构的最优化和健壮性。
设计模式(1)单例模式(Singleton)的更多相关文章
- 设计模式之单例模式——Singleton
设计模式之单例模式--Singleton 设计意图: 保证类仅有一个实例,并且可以供应用程序全局使用.为了保证这一点,就需要这个类自己创建自己的对象,并且对外有 ...
- 设计模式(4) -- 单例模式(Singleton)
设计模式(4) -- 单例模式(Singleton) 试想一个读取配置文件的需求,创建完读取类后通过New一个类的实例来读取配置文件的内容,在系统运行期间,系统中会存在很多个该类的实例对象,也就是说 ...
- 乐在其中设计模式(C#) - 单例模式(Singleton Pattern)
原文:乐在其中设计模式(C#) - 单例模式(Singleton Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 单例模式(Singleton Pattern) 作者:weba ...
- 【设计模式】单例模式-Singleton
[设计模式]单例模式-SingletonEnsure a class has only one instance, and provide a global point to access of it ...
- 设计模式之——单例模式(Singleton)的常见应用场景
单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此 ...
- 设计模式之单例模式(Singleton Pattern)
单例模式 单例模式(Singleton Pattern)在java中算是最常用的设计模式之一,主要用于控制控制类实例的数量,防止外部实例化或者修改.单例模式在某些场景下可以提高系统运行效率.实现中的主 ...
- 设计模式一: 单例模式(Singleton)
简介 单例模式是属于创建型模式的一种(另外两种分别是结构型模式,行为型模式).是设计模式中最为简单的一种. 英文单词Singleton的数学含义是"有且仅有一个元素的集合". 从实 ...
- 设计模式之——单例模式(Singleton)的常见应用场景(转):
单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此 ...
- java设计模式之 单例模式 Singleton
static 的应用 单例模式 Singleton 单例:保证一个类在系统中最多只创建一个实例. 好处:由于过多创建对象实例,会产生过多的系统垃圾,需要GC频繁回收,由于GC会占用较大的系统资源,所有 ...
- 【设计模式】单例模式 Singleton Pattern
通常我们在写程序的时候会碰到一个类只允许在整个系统中只存在一个实例(Instance) 的情况, 比如说我们想做一计数器,统计某些接口调用的次数,通常我们的数据库连接也是只期望有一个实例.Windo ...
随机推荐
- 微信开源PHP商城系统一处blind xxe(无需登录,附POC)
测试版本wemall 3.3 下载地址 http://git.oschina.net/einsqing/wemall/repository/archive?ref=master 需要开源中国的账号 c ...
- 利用cropper插件裁剪本地图片,然后将裁剪过后的base64图片上传至七牛云空间
现在做的项目需要做一些图片处理,由于时间赶急,之前我便没有处理图片,直接将图片放在input[type=file]里面,以文件的形式提交给后台,这样做简直就是最低级的做法,之后各种问题便出来了,人物头 ...
- [设计模式] Iterator - 迭代器模式:由一份奥利奥早餐联想到的设计模式
Iterator - 迭代器模式 目录 前言 回顾 UML 类图 代码分析 抽象的 UML 类图 思考 前言 这是一包奥利奥(数组),里面藏了很多块奥利奥饼干(数组中的元素),我将它们放在一个碟子上慢 ...
- TypeScript设计模式之组合、享元
看看用TypeScript怎样实现常见的设计模式,顺便复习一下. 学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到用哪个模式可以解决,UML忘了可以查,思想 ...
- Apache Flume日志收集系统简介
Apache Flume是一个分布式.可靠.可用的系统,用于从大量不同的源有效地收集.聚合.移动大量日志数据进行集中式数据存储. Flume简介 Flume的核心是Agent,Agent中包含Sour ...
- 介绍Office 365 中文用户社区 4.0
本文于2017年3月18日首发于LinkedIn,原文链接在这里 为了给广大用户提供一个可以自由交流.切磋技术的平台,微软和其他一些国际知名的大型软件公司一样,都有创建用户社区(Community,或 ...
- mybatis源码跟踪
第一步:从web.xml进入dispatcherServlet进入前端控制器 第二步:使用handlerMapping 获得handlerChain 第三步:使用适配器执行handler获取model ...
- Hibernate之关联映射(一对多和多对一映射,多对多映射)
~~~接着之前的Hibernate框架接着学习(上篇面试过后发现真的需要学习以下框架了,不然又被忽悠让去培训.)~~~ 1:Hibernate的关联映射,存在一对多和多对一映射,多对多映射: 1.1: ...
- 最短路径之BF算法+线性规划(图片格式)
- 用shell实现linux系统应用文件清理工具
用shell实现linux系统文件清理工具 1:原始需求 在系统运维中,会产生大量应用备份文件.落地文件等,这些文件需要定时清理.一般来说,都是使用crontab 拉起一个脚本来清理.类似这样: 30 ...