设计模式(1)单例模式(Singleton)

设计模式(0)简单工厂模式

源码地址

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 总结

本次主要基于上一篇的简单工厂模式,延续的学习使用了单例工厂模式确保一个类实例的全局唯一性,过程中学习了懒汉式、饿汉式、双重检查锁等具体解决方案及演变过程。

设计模式从来不是单打独斗,核心思想是要根据实际需要利用多种模式互相配合来实现代码结构的最优化和健壮性。

时间: 2024-09-30 06:37:59

设计模式(1)单例模式(Singleton)的相关文章

设计模式之单例模式——Singleton

                    设计模式之单例模式--Singleton 设计意图: 保证类仅有一个实例,并且可以供应用程序全局使用.为了保证这一点,就需要这个类自己创建自己的对象,并且对外有公开的调用方法.而且,别的类不能实例化它,所以构造方法要设置为私有的. 单例模式的要点 一是某个类只能有一个实例: 二是它必须自行创建这个实例: 三是它必须自行向整个系统提供这个实例. 例如: 有一个"单例对象",而"客户甲"."客户乙" 和&quo

java设计模式之单例模式(Singleton)

Java设计模式之单例模式 单例模式是什么? 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式如何来设计呢? 保证一个类只能有一个实例,那么我们不能无限制的new 来创建,因为我们知道,new一次就是一个新的对象,那么构造器只能私有化private -- 构造器私有化 构造器私有化了,问题又出现了,构造器私有化了,那么我们怎么来创建唯一的对象呢?-- 提供一个公有的方法/提供一个公有的静态属性 再分析一下,公有方法, 实例方法还是类方法呢?--公有的类方法(获取类实例) 依据以上

设计模式之——单例模式(Singleton)的常见应用场景(转):

单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此有些设计大师并把把其称为设计模式之一. 这里又不具体讲如何实现单例模式和介绍其原理(因为这方便的已经有太多的好文章介绍了),如果对单例模式不了解的可以先看下:http://terrylee.cnblogs.com/archive/2005/12/09/293509.html . 好多没怎么使用过的人

(转)设计模式之——单例模式(Singleton)的常见应用场景

单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此有些设计大师并把把其称为设计模式之一. 这里又不具体讲如何实现单例模式和介绍其原理(因为这方便的已经有太多的好文章介绍了),如果对单例模式不了解的可以先看下:http://terrylee.cnblogs.com/archive/2005/12/09/293509.html .当然也可以自己搜索.

设计模式之单例模式Singleton pattern

单例模式Singleton pattern 一种软件设计模式.在核心结构中只包含一个被称为单例的特殊类. 一个类只有一个对象实例,并且自行实例化向整个系统提供. 动机 一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务:一个系统只能有一个窗口管理器或文件系统:一个系统只能有一个计时工具或ID(序号)生成器.如在Windows中就只能打开一个任务管理器.如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源:如果这些窗口显示的内容不

一天一个设计模式(二) -单例模式(Singleton)

前言 单例模式 (Singleton) 是一种创建型模式,指某个类采用Singleton模式,则在这个类被创建后,只可能产生一个实例供外部访问,并且提供一个全局的访问点. 正文 (一). 优缺点 Java中单例模式 (Singleton) 是一种广泛使用的设计模式.单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在.一些管理器和控制器常被设计成单例模式. 1. 优点 提供了对唯一实例的受控访问. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象

二十四种设计模式:单例模式(Singleton Pattern)

单例模式(Singleton Pattern) 介绍保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例保证一个类仅有一个实例. Singleton using System; using System.Collections.Generic; using System.Text; namespace Pattern.Singleton { /// <summary> /// 泛型实现单例模式 /// </summary> /// <typeparam name=&q

大熊君说说JS与设计模式之------单例模式Singleton()

一,总体概要 1,笔者浅谈 顾名思义单例模式并不难理解,是产生一个类的唯一实例,在我们实际开发中也会使用到这种模式,它属于创建模式的一种,基于JS语言本身的语法特征, 对象直接量“{}”,也可以作为单例模式的一种表现形式,如下代码参考 1 function Foo(){ 2 this.bar = "Hello Singleton !" ; 3 } ; 4 var Singleton = { 5 instance : null , 6 getInstance : function(){

Android设计模式之单例模式 Singleton

一.概述 单例模式是设计模式中最简单的一种,但是它没有设计模式中的那种各种对象之间的抽象关系,所以有人不认为它是一种模式,而是一种实现技巧.单例模式就像字面的意思一样,提供一个只能自己实例化的实例,并且提供了一个全局的访问点.要达到这几点要求就要满足三点:私有构造函数(防止被别人实例化),静态私有自身对象(用来提供实例),静态公有的getInstance方法(用来创建和获取实例对象). 优缺点: 单例只允许自己建立一个实例,不需要频繁创建和销毁,可以节省内存加快对象的访问速度. 但是单例没有抽象

设计模式:单例模式(singleton)

singleton模式属于创建型设计模式.其作用是在程序设计中,对于某一个类而言,全局只能存在一个实例对象. 下面以C++为例,对单例模式进行说明: 1. 最基本单例模式(单线程) 1 class Singleton1{ 2 private: 3 static Singleton1 instance; 4 Singleton1(){} //构造函数 5 public: 6 static Singleton1 get_instance(){ 7 if (Singleton1.instance==n