欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

单例模式(Singleton Pattern)

程序员文章站 2022-05-14 08:57:06
单例模式概述 定义:确保一个类只有一个实例,并提供一个全局访问点来访问这个实例 简单的说,就是你有且只有一个女朋友(有多个女朋友的模式不是这里~~),并且你的女朋友很特殊,从来只听你的话,所以别人想和她交流(访问她)就必须通过你(全局访问点)来和她交流。 系统中用到单例模式的地方很多,比如Windo ......
  • 单例模式概述

定义确保一个类只有一个实例,并提供一个全局访问点来访问这个实例

简单的说,就是你有且只有一个女朋友(有多个女朋友的模式不是这里~~),并且你的女朋友很特殊,从来只听你的话,所以别人想和她交流(访问她)就必须通过你(全局访问点)来和她交流。

系统中用到单例模式的地方很多,比如windows系统点击开始只能出现一个开始界面,ctrl+alt+. 只能出现一个资源管理器,每个进程有且对应唯一一个进程id等等。单例模式是为了让资源得到最大化利用,不浪费资源。同时假如不采用此模式,就可能在不同时刻打开同一个界面,但界面中的内容又各不相同,因而用户极易产生误解,影响使用效率。因此单例模式在系统中的应用非常重要。

要点a.某一个单例类只能有一个实例

      b.必须自行创建这个实例

      c.必须向系统提供这个实例;

  • 单例模式的结构与实现

结构: 单例模式(Singleton Pattern)

  1. 我们考虑一个问题,每一个类都会有它的默认构造函数,或者我们重写一个构造函数,这个函数都是在创建这个类的实例的时候自动调用的。即对对象进行初始化操作。所以我们每次new的时候都会有一个新的对象,当然这是不符合单例模式的要求的。因此为了满足单例模式的要求,就必须对类中的函数进行修改,怎么修改呢,一步一步来(个人觉得这个理解还是挺重要的)。
      1. 首先,我们类的实例化不能在外部进行(单例类自行提供这个实例),即不能每次new都调用构造函数,因此将构造函数设为private类型
      2. 既然构造函数是private的,那怎么样调用呢??通过公有方法可以调用private函数,返回实例
      3. 看到这里,或许有人就有疑问了,公有方法(public ... ....)是在类的对象生成后才可以调用的,但是对象的创建又必须通过构造函数,而这里构造函数又必须通过公有方法调用,不就形成了一个鸡生蛋,蛋孵鸡的问题了吗??好了,谁先谁后,我们先辩论下吧.....其实,静态方法的使用,能很好的解决这个问题,将公有方法(函数)设为静态方法,我们就能通过类名.方法名去调用它,继而调用私有构造函数,产生对象。
      4. 再有,单例模式创建的女朋友只能是一个,那又怎么确定呢?怎么确定你的女朋友是唯一的而且你没有偷换呢?(~~皮一下),我们就要为这个单例类添加一个变量了,用来确认是否唯一,由于创建的整个入口是静态公有方法,所以在那时就要判断是否唯一了,如果该单例类有了一个女朋友,判断后便不再创建,如果当前没有,则分配一个。而可以直接访问类的静态数据和函数成员,而访问非静态成员,必须通过对象名,这有陷入鸡和蛋的问题里了,所以,该变量设为静态变量就很方便了,它也满足为整个类服务的特性。
      5. 总结下步骤 :(1)私有构造函数;(2)公有方法调用私有函数;(3)设为静态方法;(4)添加静态变量;(5)根据变量数判断是否生成女朋友(唯一的对象);
  2. 上图中:↓↓↓↓↓
  3. singleton(单例),在单例类的内部创建它的唯一实例
  4. getinstance(静态方法),通过它产生唯一实例
  5. instance(静态变量),判断是否可以产生实例

 实现:

 1 using system;
 2 using system.collections.generic;
 3 using system.linq;
 4 using system.text;
 5 
 6 namespace singleton
 7 {
 8     class singleton
 9 {
10     private static singleton instance = null;//静态私有成员变量
11     
12     //私有构造函数
13     private singleton()
14     {
15         console.writeline("恭喜你,获得一个女朋友~~");
16     }
17     
18     //静态公有方法,返回实例
19     public static singleton getinstance()
20     {
21         if(instance == null)//没女朋友
22             instance = new singleton();//生成一个吧
23         return instance;//有就返回当前的,
24     }
25 }
26     class program
27     {
28         static void main(string[] args)
29         {
30             singleton s1 = singleton.getinstance();
31             singleton s2 = singleton.getinstance();
32             if (s1 == s2)
33             {
34                 console.writeline("怎么能想要共有一个女朋友呢?s2 赶紧换一个吧...");
35             }
36         }
37     }
38 }

 结果:单例模式(Singleton Pattern)

  •  饿汉式单例和懒汉式单例

 刚看到这两个单例的名字时还是有点好笑的,如此这么生动形象呢,就好像饿汉式单身(连温饱都满足不了,何来女朋友呢),懒汉式(好吃懒做的,也很难...)不乱扯了,回主题。饿汉式单例正如饿汉一样,很饿很饿的人最想要的就是立即马上吃东西。因此饿汉式单例在定义静态变量时就实例化了单例类,因为实在太饿了啊,等不及了

单例模式(Singleton Pattern)

 1 class eagetsingleton
 2 {
 3     private static eagetsingleton instance = new eagetsingleton();//静态变量实例化单例类
 4     
 5     private eagetsingleton(){}
 6     
 7     public static eagetsingleton getinstance()
 8     {
 9         return instance;
10     }
11 }

 

单例模式(Singleton Pattern)

懒汉式单例类则是在类第一次被引用时将自己实例化,单例类被加载时不会实例化,所以这很符合懒汉的气质~但是在这里要注意的是,在定义静态变量时没有实例化单例类,而是在第一次调用静态方法时实例化单例类,这就会产生问题,高并发,多线程实现懒汉式单例时会创建多个对象,从而违背了单例模式的设计意图。也就是还是要对女朋友的个数进行判断。这要怎么办呢?在多线程的情况下,就要对该代码段进行控制,即每次只让一个线程进入并创建实例,也就是相当于现在的“共享女友”,帮你拍照啊,陪你去看电影啊 ,巴拉巴拉。但是,该“共享女友”有且只有一个,即单例类的唯一实例。所以,土豪们(各个线程)得一个一个租用,上一个用完了下一个才能租用。因此代码如下:

 1 class lazytsingleton
 2 {
 3     private static lazytsingleton instance = null;
 4     private static readonly object synroot = new object();//看做一个门。
 5     //程序运行时创建只读辅助对象
 6         
 7     private lazytsingleton(){}
 8     
 9     public static lazytsingleton getinstance()
10     {
11         if(instance == null)//在房间外问:房间里有人吗 ? 没人回应 ,可能没,可能下一秒有人进去 ,我却以为没人
12         {
13             lock(synroot)//第二次判断 //把门关了,外面线程进不来,只能里面的出来,外面的才能进
14             {
15                 if(instance == null)//继续问,房间里有人吗?  有就真的有,没有就真的没
16                 {
17                     instance = new lazytsingleton();//创建实例
18                 }
19             }
20         }
21     }
22 }

二者比较:

  • 饿汉式单例

优点:无需考虑多线程同时访问的问题,确保实例唯一性。调用速度和反应时间快于懒汉模式,因为饿汉一开始就创建,后面则直接拿来用就可以了。

缺点:不管单例对象是否需要,都会在类加载时创建,这样不如懒汉式单例,资源利用不高,且加载时间较长。如启动vs,eclipse等,需要loading许多可能要的可能不要的,要等啊...

  • 懒汉式单例

优点:第一次使用时创建,不会一直占用资源,即延迟加载。

缺点:必须考虑多线程问题,特别是单例类作为资源控制器时,会涉及资源初始化,也会耗费许多时间,也会出现多线程同时首次引用此类,造成拥堵,导致系能性能降低

 

  •  单例模式的优缺点和适用环境

  •  单例模式的优点
  1. 提供唯一实例的受控访问,可以严格控制何时访问
  2. 由于只存在一个对象,可以节省系统资源
  3. 如果将单例模式的实例数目变为可变,即将单例模式进行扩展,即可获得指定数目的实例对象,即节省资源,又提高效率(相当于多例类)
  • 单例模式的缺点
  1. 没有抽象层,扩展有较大困难
  2. 单例类职责过重,一定程度上违背了单一职责原则(单例类即提供业务方法,又提供了创建对象的方法(工厂方法),对象创建和对象本身耦合在了一起)
  3. c#、java 拥有gc(自动垃圾回收机制) (可以去了解下) ,在实例化的对象长时间不被利用,会被误认为垃圾进行自动销毁,下次利用又要重新创建,导致共享的单例对象丢失(女朋友丢了可不好受啊..)
  • 单例模式的适用环境
  1. 在系统只要一个实例对象(man只要一个girlfriend)/(资源管理器).....
  2. 客户调用类的单个实例只允许使用一个公共访问点,除了该点外,不允许其他途径来访问实例