Python面试题系列之14 什么是单例以及应用场景?

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」。


文章作者: &娴敲棋子&
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 &娴敲棋子& !
评论
  目录