身边的设计模式(一):单例 与 RedisCacheManager

大家好,以后我会用23篇文章,来给大家讲解设计模式,当然如果你看过我的项目,很多设计模式已经很会了,只是没有注意到,我这里会讲解一下,大家就会发现,如果你看懂了我的项目,其实已经至少学会了六种设计模式了。

 一、什么是单例模式

【单例模式】,英文名称:Singleton Pattern,这个模式很简单,一个类型只需要一个实例,他是属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)。

  • 1、单例类只能有一个实例。

  • 2、单例类必须自己创建自己的唯一实例。

  • 3、单例类必须给所有其他对象提供这一实例。

那咱们大概知道了,其实说白了,就是我们整个项目周期内,只会有一个实例,当项目停止的时候,实例销毁,当重新启动的时候,我们的实例又会产品。

上文中说到了一个名词【创建类型】的设计模式,那什么是创建类型的设计模式呢?

创建型(Creational)模式:负责对象创建,我们使用这个模式,就是为了创建我们需要的对象实例的。

那除了创建型还有其他两种类型的模式:

  -结构型(Structural)模式:处理类与对象间的组合

  -行为型(Behavioral)模式:类与对象交互中的职责分

这两种设计模式,以后会慢慢说到,这里先按下不表。

咱们就重点从0开始分析分析如何创建一个单例模式的对象实例。

 二、如何创建单例模式

实现单例模式有很多方法:从“懒汉式”到“饿汉式”,最后“双检锁”模式。

这里咱们就慢慢的,从一步一步的开始讲解如何创建单例,既然要创建单一的实例,那我们首先需要学会如何去创建一个实例,这个很简单,相信每个人都会创建实例,就比如说这样的:

 /// <summary>	
 /// 定义一个天气类	
 /// </summary>	
 public class WeatherForecast	
 {	
     public DateTime Date { get; set; } = DateTime.Now;	

	
     public int TemperatureC { get; set; }	

	
     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);	

	
     public string Summary { get; set; }	
 }	

	
[HttpGet]	
public WeatherForecast Get()	
{	
    // 实例化一个对象实例	
    WeatherForecast weather = new WeatherForecast();	
    return weather;	
}	

我们每次访问的时候,时间都是会变化,所以我们的实例也是一直在创建,在变化,

640?wx_fmt=gif

相信每个人都能看到这个代码是什么意思,不多说,直接往下走,我们知道,单例模式的核心目的就是:

必须保证这个实例在整个系统的运行周期内是唯一的,这样可以保证中间不会出现问题。

那好,我们改进改进,不是说要唯一一个么,好说!我直接返回不就行了:

 /// <summary>	
 /// 定义一个天气类	
 /// </summary>	
 public class WeatherForecast	
 {	
    // 定义一个静态变量来保存类的唯一实例	
    private static WeatherForecast uniqueInstance;	

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

	
    }	
    /// <summary>	
    /// 静态方法,来返回唯一实例	
    /// 如果存在,则返回	
    /// </summary>	
    /// <returns></returns>	
    public static WeatherForecast GetInstance()	
    {	
        // 如果类的实例不存在则创建,否则直接返回	
        // 其实严格意义上来说,这个不属于【单例】	
        if (uniqueInstance == null)	
        {	
            uniqueInstance = new WeatherForecast();	
        }	
        return uniqueInstance;	
    }	
    public DateTime Date { get; set; } = DateTime.Now;	

	
     public int TemperatureC { get; set; }	

	
     public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);	

	
     public string Summary { get; set; }	
 }	

然后我们修改一下调用方法,因为我们的默认构造函数已经私有化了,不允许再创建实例了,所以我们直接这么调用:


	
 [HttpGet]	
 public WeatherForecast Get()	
 {	
     // 实例化一个对象实例	
     WeatherForecast weather = WeatherForecast.GetInstance();	
     return weather;	
 }	

最后来看看效果:

640?wx_fmt=gif

这个时候,我们可以看到,时间已经不发生变化了,也就是说我们的实例是唯一的了,大功告成!是不是很开心!

但是,别着急,问题来了,我们目前是单线程的,所以只有一个,那如果多线程呢,如果多个线程同时访问,会不会也会正常呢?

这里我们做一个测试,我们在项目启动的时候,用多线程去调用:

  public WeatherForecast Get()	
  {	
      // 实例化一个对象实例	
      //WeatherForecast weather = WeatherForecast.GetInstance();	
      	
      // 多线程去调用	
      for (int i = 0; i < 3; i++)	
      {	
          var th = new Thread(	
          new ParameterizedThreadStart((state) =>	
          {	
              WriteWeather();	
          })	
          );	
          th.Start(i);	
      }	
      return null;	
  }

然后我们看看效果是怎样的,按照我们的思路,应该是只会走一遍构造函数,其实不是:

640?wx_fmt=gif

3个线程在第一次访问GetInstance方法时,同时判断(uniqueInstance ==null)这个条件时都返回真,然后都去创建了实例,这个肯定是不对的。那怎么办呢,只要让GetInstance方法只运行一个线程运行就好了,我们可以加一个锁来控制他,代码如下:

public class WeatherForecast	
     {	
        // 定义一个静态变量来保存类的唯一实例	
        private static WeatherForecast uniqueInstance;	
        // 定义一个锁,防止多线程	
        private static readonly object locker = new object();	

	
        // 定义私有构造函数,使外界不能创建该类实例	
        private WeatherForecast()	
        {	
        }	
        /// <summary>	
        /// 静态方法,来返回唯一实例	
        /// 如果存在,则返回	
        /// </summary>	
        /// <returns></returns>	
        public static WeatherForecast GetInstance()	
        {	
            // 当第一个线程执行的时候,会对locker对象 "加锁",	
            // 当其他线程执行的时候,会等待 locker 执行完解锁	
            lock (locker)	
            {	
                // 如果类的实例不存在则创建,否则直接返回	
                if (uniqueInstance == null)	
                {	
                    uniqueInstance = new WeatherForecast();	
                }	
            }	

	
            return uniqueInstance;	
        }	
        public DateTime Date { get; set; } = DateTime.Now;	

	
         public int TemperatureC { get; set; }	

	
         public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);	

	
         public string Summary { get; set; }	
     }

这个时候,我们再并发测试,发现已经都一样了,这样就达到了我们想要的效果,但是这样真的是最完美的么,其实不是的,因为我们加锁,只是第一次判断是否为空,如果创建好了以后,以后就不用去管这个 lock 锁了,我们只关心的是 uniqueInstance 是否为空,那我们再完善一下:

 /// <summary>	
 /// 定义一个天气类	
 /// </summary>	
 public class WeatherForecast	
     {	
        // 定义一个静态变量来保存类的唯一实例	
        private static WeatherForecast uniqueInstance;	
        // 定义一个锁,防止多线程	
        private static readonly object locker = new object();	

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

	
        }	
        /// <summary>	
        /// 静态方法,来返回唯一实例	
        /// 如果存在,则返回	
        /// </summary>	
        /// <returns></returns>	
        public static WeatherForecast GetInstance()	
        {	
            // 当第一个线程执行的时候,会对locker对象 "加锁",	
            // 当其他线程执行的时候,会等待 locker 执行完解锁	

	
            if (uniqueInstance == null)	
            {	
                lock (locker)	
                {	
                    // 如果类的实例不存在则创建,否则直接返回	
                    if (uniqueInstance == null)	
                    {	
                        uniqueInstance = new WeatherForecast();	
                    }	
                }	
            }	

	
            return uniqueInstance;	
        }	
        public DateTime Date { get; set; } = DateTime.Now;	

	
         public int TemperatureC { get; set; }	

	
         public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);	

	
         public string Summary { get; set; }	
     }

这样才最终的完美实现我们的单例模式!搞定。

 三、我们在哪里遇到过?

如果你看过我的 Blog.Core 项目的话,肯定看到过 Redis 那部分,我在那里就是封装了一个单例,感兴趣的可以看看:

public class RedisCacheManager : IRedisCacheManager	
    {	

	
        private readonly string redisConnenctionString;	

	
        public volatile ConnectionMultiplexer redisConnection;	

	
        private readonly object redisConnectionLock = new object();	

	
        public RedisCacheManager()	
        {	
            string redisConfiguration = Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "ConnectionString" });//获取连接字符串	

	
            if (string.IsNullOrWhiteSpace(redisConfiguration))	
            {	
                throw new ArgumentException("redis config is empty", nameof(redisConfiguration));	
            }	
            this.redisConnenctionString = redisConfiguration;	
            this.redisConnection = GetRedisConnection();	
        }	

	
        /// <summary>	
        /// 核心代码,获取连接实例	
        /// 通过双if 夹lock的方式,实现单例模式	
        /// </summary>	
        /// <returns></returns>	
        private ConnectionMultiplexer GetRedisConnection()	
        {	
            //如果已经连接实例,直接返回	
            if (this.redisConnection != null && this.redisConnection.IsConnected)	
            {	
                return this.redisConnection;	
            }	
            //加锁,防止异步编程中,出现单例无效的问题	
            lock (redisConnectionLock)	
            {	
                if (this.redisConnection != null)	
                {	
                    //释放redis连接	
                    this.redisConnection.Dispose();	
                }	
                try	
                {	
                    this.redisConnection = ConnectionMultiplexer.Connect(redisConnenctionString);	
                }	
                catch (Exception)	
                {	
                    //throw new Exception("Redis服务未启用,请开启该服务,并且请注意端口号,本项目使用的的6319,而且我的是没有设置密码。");	
                }	
            }	
            return this.redisConnection;	
        }	
        /// <summary>	
        /// 清除	
        /// </summary>	
        public void Clear()	
        {	
            foreach (var endPoint in this.GetRedisConnection().GetEndPoints())	
            {	
                var server = this.GetRedisConnection().GetServer(endPoint);	
                foreach (var key in server.Keys())	
                {	
                    redisConnection.GetDatabase().KeyDelete(key);	
                }	
            }	
        }	
        /// <summary>	
        /// 判断是否存在	
        /// </summary>	
        /// <param name="key"></param>	
        /// <returns></returns>	
        public bool Get(string key)	
        {	
            return redisConnection.GetDatabase().KeyExists(key);	
        }	

	
        /// <summary>	
        /// 查询	
        /// </summary>	
        /// <param name="key"></param>	
        /// <returns></returns>	
        public string GetValue(string key)	
        {	
            return redisConnection.GetDatabase().StringGet(key);	
        }	

	
        /// <summary>	
        /// 获取	
        /// </summary>	
        /// <typeparam name="TEntity"></typeparam>	
        /// <param name="key"></param>	
        /// <returns></returns>	
        public TEntity Get<TEntity>(string key)	
        {	
            var value = redisConnection.GetDatabase().StringGet(key);	
            if (value.HasValue)	
            {	
                //需要用的反序列化,将Redis存储的Byte[],进行反序列化	
                return SerializeHelper.Deserialize<TEntity>(value);	
            }	
            else	
            {	
                return default(TEntity);	
            }	
        }	

	
        /// <summary>	
        /// 移除	
        /// </summary>	
        /// <param name="key"></param>	
        public void Remove(string key)	
        {	
            redisConnection.GetDatabase().KeyDelete(key);	
        }	
        /// <summary>	
        /// 设置	
        /// </summary>	
        /// <param name="key"></param>	
        /// <param name="value"></param>	
        /// <param name="cacheTime"></param>	
        public void Set(string key, object value, TimeSpan cacheTime)	
        {	
            if (value != null)	
            {	
                //序列化,将object值生成RedisValue	
                redisConnection.GetDatabase().StringSet(key, SerializeHelper.Serialize(value), cacheTime);	
            }	
        }	

	
        /// <summary>	
        /// 增加/修改	
        /// </summary>	
        /// <param name="key"></param>	
        /// <param name="value"></param>	
        /// <returns></returns>	
        public bool SetValue(string key, byte[] value)	
        {	
            return redisConnection.GetDatabase().StringSet(key, value, TimeSpan.FromSeconds(120));	
        }	

	
    }

好啦,今天的设计模式你学会了么,在我的项目中,还隐藏了,工厂模式,装饰器模式,中介者模式,观察者模式,享元模式等等等等,下次再见咯。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值