C#设计模式<一>单例模式

前言:

《GOF23种设计模式》是我持续性学习的一本书。由于一直处于穿插式学习。(数据结构,设计模式,Web开发)有点儿庞杂,一方面要学习,一方面还要完成本职工作。鉴于水平有限,只能这样了。

单例模式:

我个人理解为:在面向对象编程中,实现一个唯一的对象。
官方解释为:一个类只能有一个实例,并提供一个全局访问点。


具体实现如下所示:
public  class Singleton
    {
        // 定义一个静态变量来保存类的实例
        private static Singleton uniqueInstance;

        // 定义私有构造函数,使外界不能创建该类实例
        private Singleton()
        {
        }

        /// <summary>
        /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 如果类的实例不存在则创建,否则直接返回
            if (uniqueInstance == null)
            {
                uniqueInstance = new Singleton();
            }
            return uniqueInstance;
        }
    }

实际运用:

网上各种实例都是线程同步或者ADO.NET链接数据库的案例。都用烂了。我们今天利用另外一个案例。
案例:
      我们在实际运用中,为了方便查找问题引入的一个概念:异常处理。

      异常处理最基础的就是判断这一段代码的执行结果可能会遇到什么问题:比如装箱拆箱操作。

      当我们捕获这个异常我们需要对它进行处理,我们是直接返回上一个栈还是在这里直接输出。

      我们讨论的就是输出日志(Log)。查看Log是我们解决问题的根本,上面返回了各种异常信息和堆栈信息。

      如何避免随意的打Log,这就是单例模式又一个经典案例。

         思考这样一个问题,一个程序可以对应多少个日志文件?对于一个程序或者一个业务只需要一个日志文件,我们在设计Logger类的时候都是在构

造方法或初始化方法中生成日志文件的,也就是说,这基本上等价于一个Logger的实例对应一个新的日志文件(或重新对同一文件重新开启流)。


 Logger myLogger = new Logger(@"D:\my.log");


如何才能够控制Log不被任意实例化,我们私有化构造函数,同时在Logger对象里利用静态变量实例化Logger。而为了给外部提供访问点,定义一个 公共方法GetInstance()

饿汉模式:


public class Logger
    {
        private static Logger instance = new Logger();
        private Logger() { }
        public static Logger GetInstance()
        {
            return instance;
        }
    }

以上就是单例模式的具体实现:参照上文的范式代码,于此对比参照。

*这也是单例模式的最简单实现,也叫饿汉模式,在实例化之前已经由静态变量实例化Log对象。

懒汉模式:

区别于饿汉模式的地方在于:现用现加载(延迟加载)。

改动后的用法如下:

 private static Logger instance;
        private Logger() { }
        public static Logger GetInstance()
        {
            if (instance == null)
            {
                instance = new Logger();
            }
            return instance;
        }


但是由此引发出新的问题:线程安全。
假设现在有A、B两个线程,当A线程调用了GetInstance()方法,并在Lock位置处进行了实例的空判别,并且进入了if逻辑,而这是发生了线程切换(线程切换是不可预知的,随机发生的),B线程也调用了GetInstance() 方法,其在黄色位置处也进行了判空操作,而A线程并没有完成new操作,所以B线程依然进入了if体内,准备new出实例。随后,假设线程切换回AA创建出实例并返回了实例A,而后B线程继续,Bnew除了一个实例而返回了另一个实例。那么,对于AB线程,他们所得到的实例就是不同的实例了。为了避免这种情况的发生,我们需要为其添加一个锁,来实现线程安全。

        private static object initLockHelper = new object();
        public static Logger GetInstance()
        {
            lock (initLockHelper)
            {
                if (instance == null)
                {
                    instance = new Logger();
                }
            }
            return instance;
        }

但是,这个锁的目的是为了防止首次创建对象时发生的线程问题而增加的,对于之后的更多时间里,我们是不需要再进行加锁的,这个操作的资源消耗还是比较大的,因此,我们需要在lock之前先一次检查一下instance是否为null:

public static Logger GetInstance()
{
    if (instance == null)
    {
        lock (initLockHelper)
        {
            if (instance == null)
            {
                instance = new Logger();
            }
        }
    }
    return instance;
}

这种锁机制我们称为双检锁机制,这样既保证了效率,又保证了线程安全。当我们的对象是一个轻量级类型时(类中没有太多的资源,比较简单)这是应该优先考虑使用饿汉模式,而对于类型复杂、资源占用较多的对象,可以考虑现用现加载,即懒汉模式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值