vlambda博客
学习文章列表

听说你的程序还不会计划生育?(单例模式理论篇)



导语:单例模式可是设计模式中最常见、最简单的模式之一,也是教育机构中最常提的设计模式,因为在实际面试中也是频频出现,今天我们就好好谈谈设计模式中的计划生育——单例模式


其实开始介绍单例模式之前,该和大家讲讲什么是面向对象,什么是面对对象的封装、继承、多态三大特性,什么是面对对象的五大原则的。

但是单讲这些特性、原则实在是枯燥乏味,之后我会单独把这些特性、原则和大家列出来,顺便讨论一下,现在就先跟着我从简单的单例模式看看,以面对对象出家的设计模式到底是什么妖魔鬼怪。

什么是单例模式


单例模式属于设计模式中的创建型模式,其目的就是提供一种创建对象的最佳方法。

那到底什么是单例模式呢,简单的说, 例模式就是整个系统有且只有一个实例对象存在 ,换句话说,就是希望给这个类进行计划生育。

听说你的程序还不会计划生育?(单例模式理论篇)

当你希望整个系统中,某个类只有一个实例,而且这个实例还是一个全局对象,顺便还要提供访问这个实例的全局节点时,那么你的所有需求就是单例模式的全部特点,完美匹配。

单例模式解决的问题


在码农的路上走的越远,越觉得万事万物皆看需求。离开需求谈开发,一定是排泄某种气体。

所以我们学习单例模式前,就来看看单例模式到底是解决什么问题。如果用一句话概括,那就是要解决, 适用于全局的类反复的创建与销毁实例对象所带来的不必要的资源浪费问题

好吧,上面这句话看着就很麻烦,我们把它拆分一下,你会发现单例模式所解决的问题,就是它的特点。

| 一个类一个实例

首先要保证一个类只能创建一个实例对象吧,不去控制一个类所拥有的实例对象的创建,那造成的内存资源的浪费只能让你哑巴吃黄连,有苦说不出。

上述的这种情况最常见的就是在某个服务器的配置信息上。举个例子,服务器的配置文件需要客户端通过一个类来读取,如果在程序的运行过程中,配置文件多次被调用,那这个类将创建多个实例对象,资源浪费不言而喻吧。

而单例模式就可以轻松解决了,这些配置数据交由一个单例对象来读取,然后服务器进程中的其他对象通过访问这个单例对象来获取配置信息。

从头到尾, 没有实例对象的反复创建和销毁,自然也谈不上内存资源的浪费了

| 单例拥有全局访问节点

还是以上述的配置信息为例,这些配置信息可以说是全局变量了吧,你在统一使用的时候方便是方便但也很不安全。

没办法,这些变量一旦被更改,你的程序可就要 Run Bug 了,还贼不好找,别问我怎么知道,都是血的教训。

像这样的对象是绝不能被其他代码所覆盖的,而且你也不希望配置信息散落的哪里都是吧,到时候乱七八糟的找都找不到。

所以 我们要有一个可以集中访问、调用、更改的全局访问节点,方便+实用。没错,就是你无法抗拒的单例类

单例模式严格说来是解决了上述两个问题,那它可就违反了面向对象的原则之一,单一职责原则。

单一职责原则可以说是五大原则中最简单的原则了,它要求一个类所解决的问题必须单一,复杂的责任会带来效率的低下。

但是瑕不掩瑜嘛,何况单例模式在现如今已经很常见了,解决其中一个问题就可以称作单例模式了,关于这一点我们就不再过多纠结了。

单例模式的适用场景


单例模式可是出现在方方面面,只是很少被人发现罢了。

在计算机操作系统中,有很多地方都被设计成单例,谁让单例可以简化在复杂环境下的配置管理呢。

以 Windows 中的任务管理器为例,就使用了单例模式,只能打开一个任务管理器窗口,这样就可以避免因为多个任务管理器窗口导致的内存资源浪费或者各窗口导致的内容不一致的毛病。

像任务管理器的例子比比皆是,我给大家盘点一下应用场景:

  • Windows 的回收站可是经典的单例情景,对于整个操作系统的运行,回收站一直维护着唯一实例。
  • Windows 的任务管理器,刚刚提过了就不赘述了。
  • 网站的计数器,为了方便同步,往往也采用的是单例模式。
  • 应用程序的日志应用也是经典的单例模式,这是因为共享日志文件要长期处于打开状态,为了同步和追加新的内容,只能操作一个实例对象。
  • 操作系统的文件系统,也属于单例模式,一个操作系统只能有一个文件系统,要不文件操作将变得乱七八糟。
  • 网页应用的配置信息的读取,由于之前提过的资源共享,所以一般也都采用单例模式。
  • 多线程的线程池的设计一般也要采用单例模式,因为这样方便线程池对当下线程的控制。
  • 数据库连接池的设计往往采用单例模式,别怀疑这是真的,单例模式可以降低连接数据库导致的内耗。主要体现在节省了打开或者关闭数据库连接导致的效率损耗。
  • 输出硬件往往都是以单例模式设计的,以最经典的打印机为例,每台电脑有若干打印机,为了避免两个打印任务同时分配给同一打印机,就需要设计成单例。
  • 各种软件的属性文件存放给系统配置中,而系统管理这些属性文件也都是采用了单例模式的设计思路,也是为了避免权限操作混乱等各种问题。

上面说的这几种实际的应用场景总结下来可分为两类:

如果程序中有一对多的情景,某个类将对于所有客户端只有一个可用实例时,可以使用单例模式

如果你需要更方便的控制全局变量,减少某些没必要的内耗,也可以使用单例模式

单例模式的注意事项


单例模式怕多线程的应用场所,因为执行速度太快。

如果唯一的实例还没有被创建,而有两个甚至两个以上的线程同时调用单例的创建方法,它们可能会同时判断并没有唯一实例。

导致的结果就是各自创建了一个实例,那还怎么能称为单例呢,后续程序的崩溃是一种必然。

至于解决办法更直接就是俩字: 加锁

不了解多线程、多进程的小伙伴应该会比较陌生,简单的说就是加锁的部分按顺序依次执行,未加锁的部分再并发执行。

通过牺牲部分的执行速度,来保证实例的唯一性,也可保证数据的安全。

这种需要加锁控制的单例模式,造成了巨大的时间浪费不说,还没有充分利用 CPU 的并发优势, 所以真正在使用单例模式时要权衡利弊

结尾


看来一篇是讲不完单例模式了,我找了五种 Python 实现单例模式的方法,下一篇再和大家慢慢道来。

未完待续~