1/23 创建型设计模式:单例模式

介绍

单例模式是23种设计模式最为简单的一种,属于创建型设计模式中的一种。

其中最主要就是保证一下代码特性:

  • 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

设计模式是编码思想的一种应用,如果博客中涉及到对于涉及模式的将会采用C#、Java两种语言均实现一次,实质上没有太大的区别。如果有可能会另外采用Python并结合语言特性单独写一篇Python中对于设计模式的实现。
由于本人主修C#,故会由C#推演JAVA

任何代码实现当前业务后便是一个胜利,但是如何设计一个富有弹性、软件调用方便的代码结构才是体现架构师功力的点。

  • 如果确认代码不会发生改变不需要过多的耦合设计模式,不需要为了Consle.Writeline(“Hello World”)或System.Out.Println(“Hello World”)专门去使用设计模式。
  • 但是当需求迭代或者逐渐复杂的时候,你去重构,并使用设计模式组织当前的业务,设计后续可能扩展的代码编写分区(即代码如何继承、编写在那个类库/包下)

语言实现

CSharp

1.V1版本,正常编写代码

单例对象代码:

/// <summary>
/// 系统设置信息
/// </summary>
public class SystemConfig
{
    /// <summary>
    /// 实例化系统设置信息
    /// </summary>
    /// <param name="strName"></param>
    /// <param name="strDescInfo"></param>
    public SystemConfig(string strName,string strDescInfo)
    {
        Name = strName;
        DescInfo = strDescInfo;
        Thread.Sleep(1000);
    }

    /// <summary>
    /// 系统名称
    /// </summary>
    public string Name
    {
        get;
        set;
    }

    /// <summary>
    /// 系统描述
    /// </summary>
    public string DescInfo
    {
        get;
        set;
    }

    public override string ToString()
    {
        return $"Name:{Name},DescInfo:{DescInfo}";
    }
}

界面调用:

    private string SystemName = "单例实现";
    private string DescInfo = "介绍单例模式使用过程中各种细节";
    public MainForm()
    {
        InitializeComponent();
    }

    /// <summary>
    /// 显示系统简介信息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnEasy_Click(object sender, EventArgs e)
    {
        SystemConfig pSystemConfig = new SystemConfig(SystemName, DescInfo);
        Console.WriteLine(pSystemConfig);
    }

2.V2版本,由于软件多个地方均需要使用系统设置信息,如下:

  1. 坏气味实现方式:

     /// <summary>
     /// 显示系统简介信息
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     private void btnEasy_Click(object sender, EventArgs e)
     {
         SystemConfig pSystemConfig = new SystemConfig(SystemName, DescInfo);
         Console.WriteLine(pSystemConfig);
     }
    
     /// <summary>
     /// 其他业务:显示系统简介信息
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     private void btnEasy1_Click(object sender, EventArgs e)
     {
         SystemConfig pSystemConfig = new SystemConfig(SystemName, DescInfo);
         Console.WriteLine(pSystemConfig);
     }
    

代码中按照界面需要使用SystemConfig对象,何处实现便何处实例并调用,会出现如下不和谐的问题:

  • SystemConfig需要统一添加参数,需要扩展参数时。
  • 回到最原始SystemConfig配置类中,为了模拟实例过程中与数据库或配置文件交互中可能造成的效率损失,增加了一个Thread.Sleep(1000)等待1000秒。如果多出实例次数过多的话,如:需要循环调用1000次时我便需要总共等待Thread.Sleep(1000*1000)合计1000秒。

在这个场景下,实质上便是将SystemConfig对象实例随着业务的变化不断升级SystemConfig对象的级别的过程

3.V3版本,升级对象作用域,如下:

  • 如果只是此类使用则再此类中增加全局的SystemConfig对象如:

      public partial class MainForm : Form
      {
          private string SystemName = "单例实现";
          private string DescInfo = "介绍单例模式使用过程中各种细节";
    
          SystemConfig CurrentSystemConfig = null;
    
          public MainForm()
          {
              InitializeComponent();
              CurrentSystemConfig = new SystemConfig(SystemName, DescInfo);
          }
    
          /// <summary>
          /// 显示系统简介信息
          /// </summary>
          /// <param name="sender"></param>
          /// <param name="e"></param>
          private void btnEasy_Click(object sender, EventArgs e)
          {
              Console.WriteLine(CurrentSystemConfig);
          }
    
          /// <summary>
          /// 其他业务:显示系统简介信息
          /// </summary>
          /// <param name="sender"></param>
          /// <param name="e"></param>
          private void btnEasy1_Click(object sender, EventArgs e)
          {
              Console.WriteLine(CurrentSystemConfig);
          }
      }
    

基于V3版本便已经是在一定程度上使用的了单例模式,

  • 但作用域我们大胆一点将其提取到代码中可以一定知道的地方。
    代码如下:

SystemConfig部分:

/// <summary>
/// 系统设置信息
/// </summary>
public class SystemConfig
{
    private static string SystemName = "单例实现";
    private static string SystemDescInfo = "介绍单例模式使用过程中各种细节";

    private static SystemConfig m_CurrentSystemConfig = null;
    public static SystemConfig CurrentSystemConfig
    {
        get
        {
            if (m_CurrentSystemConfig == null)
            {
                m_CurrentSystemConfig = new SystemConfig(SystemName, SystemDescInfo);
            }
            return m_CurrentSystemConfig;
        }
    }

    /// <summary>
    /// 实例化系统设置信息
    /// </summary>
    /// <param name="strName"></param>
    /// <param name="strDescInfo"></param>
    public SystemConfig(string strName,string strDescInfo)
    {
        Name = strName;
        DescInfo = strDescInfo;
        Thread.Sleep(1000);
    }

    /// <summary>
    /// 系统名称
    /// </summary>
    public string Name
    {
        get;
        set;
    }

    /// <summary>
    /// 系统描述
    /// </summary>
    public string DescInfo
    {
        get;
        set;
    }

    public override string ToString()
    {
        return $"Name:{Name},DescInfo:{DescInfo}";
    }
}

界面调用:

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    /// <summary>
    /// 显示系统简介信息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnEasy_Click(object sender, EventArgs e)
    {
        Console.WriteLine(SystemConfig.CurrentSystemConfig);
    }

    /// <summary>
    /// 其他业务:显示系统简介信息
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void btnEasy1_Click(object sender, EventArgs e)
    {
        Console.WriteLine(SystemConfig.CurrentSystemConfig);
    }
}

4.懒汉单例与饿汉单例模式的区别

两种单例模式先讲述饿汉单例模式再将懒汉单例

  • 饿汉单例模式是不论你是否需要使用,我都需要将单例实例化;

  • 懒汉单例顾名思义便是很懒,而在代码中的体现便是按需,即我使用时才用

    • 两者的区别便是对于单例的初始化的动作的地方。饿汉便是我不论你是否使用我,我均将对象实例出来,而懒汉是你使用单例实例时我才实例化我所需的对象。

以下代码均采用部分代码,其他部分均为相同,如需向上文中获取拼接
以下为饿汉单例模式的部分代码:

  • 使用代码调用的方式实现饿汉单例模式

      /// <summary>
      /// 系统设置信息
      /// </summary>
      public class SystemConfig
      {
          private static string SystemName = "单例实现";
          private static string SystemDescInfo = "介绍单例模式使用过程中各种细节";
    
          private static SystemConfig m_CurrentSystemConfig = null;
          public static SystemConfig CurrentSystemConfig
          {
              get
              {
                  return m_CurrentSystemConfig;
              }
              set
              {
                  m_CurrentSystemConfig = value;
              }
          }
    
          public static void CreateSingleton()
          {
              CurrentSystemConfig = new SystemConfig(SystemName, SystemDescInfo);
          }
      }
    

    在此段代码中使用代码调用CreateSingleton的方式加载饿汉单例,方便其他环节使用。

  • 使用语言优势实现饿汉单例模式

    ///
    /// 系统设置信息
    ///
    public class SystemConfig
    {
    private static string SystemName = “单例实现”;
    private static string SystemDescInfo = “介绍单例模式使用过程中各种细节”;

      private static SystemConfig m_CurrentSystemConfig = null;
      public static SystemConfig CurrentSystemConfig
      {
          get
          {
              return m_CurrentSystemConfig;
          }
          set
          {
              m_CurrentSystemConfig = value;
          }
      }
    
    
      static SystemConfig()
      {
          CurrentSystemConfig = new SystemConfig(SystemName, SystemDescInfo);
      }
    

    }
    在此段代码中使用语言上提供的遍历使用静态构造函数static SystemConfig的遍历,实现只要使用改类时便将单例实例创建。

以下为懒汉单例模式的部分代码,其为V3版本中的部分代码

/// <summary>
/// 系统设置信息
/// </summary>
public class SystemConfig
{
    private static string SystemName = "单例实现";
    private static string SystemDescInfo = "介绍单例模式使用过程中各种细节";

    private static SystemConfig m_CurrentSystemConfig = null;
    public static SystemConfig CurrentSystemConfig
    {
        get
        {
            if (m_CurrentSystemConfig == null)
            {
                m_CurrentSystemConfig = new SystemConfig(SystemName, SystemDescInfo);
            }
            return m_CurrentSystemConfig;
        }
    }

    /// <summary>
    /// 实例化系统设置信息
    /// </summary>
    /// <param name="strName"></param>
    /// <param name="strDescInfo"></param>
    public SystemConfig(string strName,string strDescInfo)
    {
        Name = strName;
        DescInfo = strDescInfo;
        Thread.Sleep(1000);
    }
}

该代码为再使用SystemConfig.CurrentSystemConfig对象时才会构建对象。

两种单例模式(饿汉单例模式、懒汉单例模式均存在一定程度上的不足)

  • 饿汉单例模式的不足为:代码需要在需要使用的地方之前,调用创建单例的函数,创建出饿汉单例,一方面代码不优雅、可读性不强,另一方面会降低应用程序的效率[特殊场景下]
  • 懒汉单例模式会出现线程安全,如果多个线程同时使用该单例,在一定场景下将会出现多个线程使用的不是同一个单例。

5.V4版本使用懒汉模式,加上双重判断锁保证对象的线程安全。

  • 1.首先描述懒汉单例模式会出现的线程安全是一个什么场景以及为什么会出现这样的一个问题。
    当程序接入多线程时,多个线程同时访问单例时会出现,先运行的部分线程使用的单例不是同一个对象。如我们创建如下场景:

SystemConfig懒汉单例实现方式:

    /// <summary>
    /// 系统设置信息
    /// </summary>
    public class SystemConfig
    {
        private static string SystemName = "单例实现";
        private static string SystemDescInfo = "介绍单例模式使用过程中各种细节";

        private static SystemConfig m_CurrentSystemConfig = null;
        public static SystemConfig CurrentSystemConfig
        {
            get
            {
                if (m_CurrentSystemConfig == null)
                {
                    m_CurrentSystemConfig = new SystemConfig(SystemName, SystemDescInfo);
                }
                return m_CurrentSystemConfig;
            }
            set
            {
                m_CurrentSystemConfig = value;
            }
        }

        /// <summary>
        /// 实例化系统设置信息
        /// </summary>
        /// <param name="strName"></param>
        /// <param name="strDescInfo"></param>
        public SystemConfig(string strName,string strDescInfo)
        {
            Name = strName;
            DescInfo = strDescInfo;
            Thread.Sleep(1000);
        }

        /// <summary>
        /// 系统名称
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// 系统描述
        /// </summary>
        public string DescInfo
        {
            get;
            set;
        }

        public override string ToString()
        {
            return $"Name:{Name},DescInfo:{DescInfo}";
        }
    }

界面端多线程使用单例;

    public int Num
    {
        get
        {
            return Int32.Parse(txtNum.Text);
        }
        set
        {
            txtNum.Text = value.ToString();
        }
    }

    private void btnMulitThreadSingleton_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < Num; i++)
        {
            Task pTask = new Task(() =>
            {
                ShowSystemConfig();
            });
            pTask.Start();
        }
    }

    public void ShowSystemConfig()
    {
        Console.WriteLine($"对象实例ID:{SystemConfig.CurrentSystemConfig.GetHashCode()}   信息{SystemConfig.CurrentSystemConfig}");
    }

出现问题如下:
在这里插入图片描述
可以看到在一开始的实例中出现的对象的HashCode值不是一个。若我们现在需要解决此问题便是使用线程锁解决此问题。

  • 增加锁解决线程同步和线程安全。

     /// <summary>
     /// 系统设置信息
     /// </summary>
     public class SystemConfig
     {
         private static string SystemName = "单例实现";
         private static string SystemDescInfo = "介绍单例模式使用过程中各种细节";
    
         private static SystemConfig m_CurrentSystemConfig = null;
    
         private static Object MutilThreadLock = new Object();
    
         public static SystemConfig CurrentSystemConfig
         {
             get
             {
                 lock (MutilThreadLock)
                 {
                     if (m_CurrentSystemConfig == null)
                     {
                         m_CurrentSystemConfig = new SystemConfig(SystemName, SystemDescInfo);
                     }
                 }
                 return m_CurrentSystemConfig;
             }
             set
             {
                 m_CurrentSystemConfig = value;
             }
         }
     }
    

    但是使用该锁文件来解决问题,但是多线程会在大并发访问对象时会比较慢,因为线程的访问均会lock住,所以会造成锁定的问题。

  • 使用双重严重锁解决该问题

     /// <summary>
     /// 系统设置信息
     /// </summary>
     public class SystemConfig
     {
         private static string SystemName = "单例实现";
         private static string SystemDescInfo = "介绍单例模式使用过程中各种细节";
    
         private static SystemConfig m_CurrentSystemConfig = null;
    
         private static Object MutilThreadLock = new Object();
    
         public static SystemConfig CurrentSystemConfig
         {
             get
             {
                 if (m_CurrentSystemConfig == null)
                 {
                     lock (MutilThreadLock)
                     {
                         if (m_CurrentSystemConfig == null)
                         {
                             m_CurrentSystemConfig = new SystemConfig(SystemName, SystemDescInfo);
                         }
                     }
                 }
                 return m_CurrentSystemConfig;
             }
             set
             {
                 m_CurrentSystemConfig = value;
             }
         }
     }
    

6.V5线程单例实现方式

线程单例和单例模式有一定的区别,当软件中全局只用一个实例信息时,单例模式完全可以使用6中的解决方案便可以应对。而当各个线程中只需要一个实例时,主线程与各个子线程使用相同的对象不同的实例,以解决多线程中的业务场景。

public class MutilSystemConfig
{
    private static string SystemName = "单例实现";
    private static string SystemDescInfo = "介绍单例模式使用过程中各种细节";

    /// <summary>
    /// 线程数据槽
    /// </summary>
    private static string key = "MutilSystemConfig-Single";
    private static Object SingeletonLock = new Object();

    public static MutilSystemConfig CurrentSystemConfig
    {
        get
        {
            MutilSystemConfig pMutilSystemConfig = CallContext.GetData(key) as MutilSystemConfig;
            if (pMutilSystemConfig == null)
            {
                lock (SingeletonLock)
                {
                    MutilSystemConfig pTempMutilSystemConfig = new MutilSystemConfig(SystemName, SystemDescInfo, Thread.CurrentThread.ManagedThreadId);
                    pMutilSystemConfig = CallContext.GetData(key) as MutilSystemConfig;
                    if (pMutilSystemConfig == null)
                    {
                        pMutilSystemConfig = pTempMutilSystemConfig;
                        CallContext.SetData(key, pMutilSystemConfig);
                    }
                }
            }
            return pMutilSystemConfig;
        }
    }


    /// <summary>
    /// 实例化系统设置信息
    /// </summary>
    /// <param name="strName"></param>
    /// <param name="strDescInfo"></param>
    public MutilSystemConfig(string strName, string strDescInfo,int currentThreadID)
    {
        Name = strName;
        DescInfo = strDescInfo;
        CurrentThreadID = currentThreadID;
    }

    /// <summary>
    /// 系统名称
    /// </summary>
    public string Name
    {
        get;
        set;
    }

    /// <summary>
    /// 系统描述
    /// </summary>
    public string DescInfo
    {
        get;
        set;
    }

    public int CurrentThreadID
    {
        get;
        set;
    }

    public override string ToString()
    {
        return $"CurrentThreadID:{CurrentThreadID}";
    }
}

界面调用:

    private void btnMulitThreadSingleton_Click_1(object sender, EventArgs e)
    {
        for (int i = 0; i < Num; i++)
        {
            Task pTask = new Task(() =>
            {
                ShowMutilSystemConfig();
            });
            pTask.Start();
        }
    }

    public void ShowMutilSystemConfig()
    {
        Console.WriteLine($"当前线程ID:{Thread.CurrentThread.ManagedThreadId.ToString()}  对象实例ID:{MutilSystemConfig.CurrentSystemConfig.GetHashCode()}  信息{MutilSystemConfig.CurrentSystemConfig} ");
    }
实现效果如下:

在这里插入图片描述

上述代码以及截图可以看到每一个线程都是一个线程实例信息。

设计思路:

  • 通过线程ID形成字典信息,每一个线程判断一下线程字典中是否包含该对象,存在则返回一个对象。那么后台存储信息则保存为Dictionary<int,object>
  • 当线程销毁时需要将Dictionary字典中的信息移除。
    之所以需要移除字典项我们观察一下上图可以看到线程ID不是唯一的,当线程开启后,CPU会为线程随机分配一个不在当前线程池中的ID信息。也就是说线程ID会出现重复,在严谨意义上不同时间线上的线程ID相同不能判断时同一个线程。

基于此思路,但是在.Net框架中提供了一个数据结构可以完成该业务需求,为[数据槽]也就是上文CallContext.GetData(key)。

7.单例模式C#中的小技巧

  1. 静态构造函数Static Object() 天生的单例实现方式。

  2. 使用单例封装之后可以将对象闭包,将对象的实例化函数私有化private不允许其他人实例生成,避免其他调用人员再生成而导致代码上的不整洁或者为后续重构升级挖坑:

    public class MutilSystemConfig
    {
    private static string SystemName = “单例实现”;
    private static string SystemDescInfo = “介绍单例模式使用过程中各种细节”;

     /// <summary>
     /// 线程数据槽
     /// </summary>
     private static string key = "MutilSystemConfig-Single";
     private static Object SingeletonLock = new Object();
    
     public static MutilSystemConfig CurrentSystemConfig
     {
         get
         {
             MutilSystemConfig pMutilSystemConfig = CallContext.GetData(key) as MutilSystemConfig;
             if (pMutilSystemConfig == null)
             {
                 lock (SingeletonLock)
                 {
                     MutilSystemConfig pTempMutilSystemConfig = new MutilSystemConfig(SystemName, SystemDescInfo, Thread.CurrentThread.ManagedThreadId);
                     pMutilSystemConfig = CallContext.GetData(key) as MutilSystemConfig;
                     if (pMutilSystemConfig == null)
                     {
                         pMutilSystemConfig = pTempMutilSystemConfig;
                         CallContext.SetData(key, pMutilSystemConfig);
                     }
                 }
             }
             return pMutilSystemConfig;
         }
     }
    
    
     /// <summary>
     /// 实例化系统设置信息
     /// </summary>
     /// <param name="strName"></param>
     /// <param name="strDescInfo"></param>
     private MutilSystemConfig(string strName, string strDescInfo,int currentThreadID)
     {
         Name = strName;
         DescInfo = strDescInfo;
         CurrentThreadID = currentThreadID;
     }
    

    }

JAVA

1.懒汉双重判断锁

单例代码实现

/// <summary>
/// 系统设置信息
/// </summary>
public class SystemConfig {
    private static String SystemName = "单例实现";
    private static String SystemDescInfo = "介绍单例模式使用过程中各种细节";

    private static SystemConfig m_CurrentSystemConfig = null;

    public static SystemConfig GetSystemConfig() {
        if (m_CurrentSystemConfig == null) {
            synchronized (SystemConfig.class) {
                if (m_CurrentSystemConfig == null) {
                    m_CurrentSystemConfig = new SystemConfig(SystemName, SystemDescInfo);
                }
            }
        }
        return m_CurrentSystemConfig;
    }

    /// <summary>
    /// 实例化系统设置信息
    /// </summary>
    /// <param name="strName"></param>
    /// <param name="strDescInfo"></param>
    public SystemConfig(String strName, String strDescInfo) {
        Name = strName;
        DescInfo = strDescInfo;
    }

    /// <summary>
    /// 系统名称
    /// </summary>
    public String Name;

    /// <summary>
    /// 系统描述
    /// </summary>
    public String DescInfo;

    @Override
    public String toString(){
        return "Name="+Name+" DescInfo="+DescInfo;
    }
}

界面调用

package com.XCai;

public class Main {

    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            ThreadTest pThreadTest= new ThreadTest();
            pThreadTest.start();
        }
    }
}

----线程

package com.XCai;

public class ThreadTest extends Thread {
    public void run() {
        System.out.println("对象HashCode"+SystemConfig.GetSystemConfig().hashCode()+"对象信息:"+SystemConfig.GetSystemConfig());

    }
}
已标记关键词 清除标记
1) 优秀的程序应该是这样的:阅读时,感觉很优雅;新增功能时,感觉很轻松;运行时,感觉很快速,这就需要设计模式支撑。<br /> <br /> 2) 设计模式包含了大量的编程思想,讲授和真正掌握并不容易,网上的设计模式课程不少,大多讲解的比较晦涩,没有真实的应用场景和框架源码支撑,学习后,只知其形,不知其神。就会造成这样结果: 知道各种设计模式,但是不知道怎么使用到真实项目。本课程针对上述问题,有针对性的进行了升级 (1) 授课方式采用 图解+框架源码分析的方式,让课程生动有趣好理解 (2) 系统全面的讲解了设计模式,包括 设计模式七大原则、UML类图-类的六大关系、23设计模式及其分类,比如 模式的8种实现方式、工厂模式的3种实现方式、适配器模式的3种实现、代理模式的3种方式、深拷贝等<br /> <br /> 3) 如果你想写出规范、漂亮的程序,就花时间来学习下设计模式吧<br /> <br /> 课程内容和目标<br /> <br /> 本课程是使用Java来讲解设计模式,考虑到设计模式比较抽象,授课采用 图解+框架源码分析的方式<br /> <br /> 1) 内容包括: 设计模式七大原则(一职责、接口隔离、依赖倒转、里氏替换、开闭原则、迪米特法则、合成复用)、UML类图(类的依赖、泛化和实现、类的关联、聚合和组合) 23设计模式包括:创建模式模式(8种实现)、抽象工厂模式、原模式、建造者模式、工厂模式。结构模式:适配器模式(3种实现)、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式(3种实现)。行为模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)<br /> <br /> 2) 学习目标:通过学习,学员能掌握主流设计模式,规范编程风格,提高优化程序结构和效率的能力。<br /> <div> <br /> </div>
相关推荐
©️2020 CSDN 皮肤主题: 点我我会动 设计师:白松林 返回首页