Python面试题系列之14: 什么是单例以及应用场景?
Question
什么是单例?请简述其使用场景。
知识点详解
先从一个例子说起。
假设我们去4S店买车,一下子提了两辆车。
# 定义一个 MyCar 类
class MyCar():
pass
Tesla_Model_3 = MyCar()
Tesla_Model_X = MyCar()
我们查询下我们的订单号
print(f'Tesla_Model_3 OrderID:{id(Tesla_Model_3)}')
print(f'Tesla_Model_X OrderID:{id(Tesla_Model_X)}')
Tesla_Model_3 OrderID:2698785399304
Tesla_Model_X OrderID:2698785399360
这里出现两个不同的订单号,不合理啊。我们同时提了两辆车,订单号应该是同一个才对啊。
这时单例对象就派上用场了。
单例模式(Singleton Pattern),它是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
# 定义一个 MyCar 类
class MyCar():
# 定义一个类属性保存对象
__instance = None
def __new__(cls, *args, **kwargs):
# 如果 instance 为空证明是第一次创建实例
if cls.__instance == None:
# 通过父类的__new__(cls)创建实例
cls.__instance = object.__new__(cls)
return cls.__instance
else:
# 返回上一个对象的引用
return cls.__instance
Tesla_Model_3 = MyCar()
Tesla_Model_X = MyCar()
这里我们先把类属性初始化为 None,每次都对类属性做判断,如果类属性为 None,表示第一次创建,我们通过父类的__new__(cls)
来创建实例。如果类属性不为 None,表示之前已经创建过了,直接返回上一个对象的实例就行。
我们现在再次打印看看。
print(f'Tesla_Model_3 OrderID:{id(Tesla_Model_3)}')
print(f'Tesla_Model_X OrderID:{id(Tesla_Model_X)}')
Tesla_Model_3 OrderID:2686250262992
Tesla_Model_X OrderID:2686250262992
通过上述方式改造后,我们看到此时两台车的订单号已经是相同了。无论通过哪台车,都能得到我们在购车时的订单号。
我们再来看看以下几种场景:
- Windows的资源管理器
- 网站计数器
- 线程池、数据库连接池等资源池
- Python中用以日志记录的logger
Windows的资源管理器,在Win桌面中我们打开一个回收站,这时当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。
事实上,我们在使用过程中并不存在需要同时打开两个回收站窗口的必要性。假如我每次创建回收站时都需要消耗大量的资源,而每个回收站之间资源是共享的,那么在没有必要多次重复创建该实例的情况下,创建了多个实例,这样做就会给系统造成不必要的负担,造成资源浪费。
再举一个栗子,网站的计数器。如果存在有多个计数器,每一个用户的访问都刷新计数器的值,这样的话记录的数值是难以同步的。但是如果采用单例模式实现就不会存在这样的问题,而且还可以避免线程安全问题。
同样的,多线程的线程池的设计一般也是采用单例模式,这是由于线程池需要方便对池中的线程进行控制。
上述这些使用场景中用到的其实都是单例模式。为什么一定要用单例模式,归纳来说:
- 可以避免对资源的多重占用
- 避免对同一个资源文件的同时操作,造成文件状态不一致
- 减少内存开支,尤其是需要频繁创建和使用的类
- 设置全局访问点,优化和共享资源访问
在协同开发中为了减少产生类似的实例对象来节省资源,以及整体项目中需要一个共享访问节点或共享数据的场景中,使用单例模式来开发是再合适不过了。
Answer
单例模式,保证一个类仅有一个实例,并提供一个访问他的全局访问点。
在协同开发中为了减少产生类似的实例对象来节省资源,以及整体项目中需要一个共享访问节点或共享数据的场景中,使用单例模式来开发是再合适不过了,具体场景可参考文中给出的例子。
后记
当然使用单例模式的前提,是我们的确可以用一个实例就能搞定要解决的问题,并不需要多个实例。如果每个实例都需要维护自己的状态,这种情况下单例模式肯定是不适用的。本篇介绍了单例模式的定义及使用场景,关于单例模式的实现,你又有多少种方式呢?
好了,以上就是本篇全部内容。
备注:本篇首发于知识星球「人人都是Pythonista」。