无论是在python代码中,还是面试中单例设计模式都是经常被问到和使用的,例如面试中会让你用代码实现单例模式分几种不同的方式,或者问你在平常工作中哪些地方有用到单例设计模式,然后深入探讨。
在本文中我将针对这两个问题来回答和用python代码来编写我们的单例模式。
首先,我们要了解什么是单例模式--官方解释是:确保一个类只有一个实例(也就是类的对象),并且提供一个全局的访问点(外部通过这个访问点来访问该类的唯一实例)。通俗的说就是,无论类实例化多少次,都只有一个相同的对象产生,并且可以通过一个具柄去访问这个这个唯一实例。
其次,我们平时代码中碰到是用单例模式设计的代码有哪些呢?
应用框架的配置文件config(如flask,django框架的配置文件)
线程池,数据库连接池的创建
应用程序的日志应用
这些应用都有一个共性那就是:他们都是全局共享的对象不会发生变化,不希望重复创建,这样有利于节省空间和减少性能消耗。
python实现单例模式的几种方式分别有:
一、类方法实现
classSingleType:
_instance=None
@classmethoddefinstance(cls):if notcls._instance:
cls._instance=SingleType()returncls._instancefor i in range(100):print(SingleType.instance())
看一下打印结果:
返回的都是同一个对象,实现了我们说的,无论调用多少次,都只返回同一个实例,但是这在多线程的环境中,可能会存在返回多个实例,我们实验一下。
import threading
import time
class SingleType:
_instance = None
def __init__(self):
time.sleep(1)
super().__init__()
@classmethod
def instance(cls):
if not cls._instance:
cls._instance = SingleType()
return cls._instance
def task():
obj = SingleType.instance()
print(obj)
for i in range(10):
t = threading.Thread(target=task).start()
print(SingleType.instance())
这是多线程环境测试的代码结果如下:
可以看到在多线程的环境中,我们产生了两个2个对象,这就违背了我们的单例原则了,那怎么解决它呢,答案是加锁。
importthreadingimporttimeclassSingleType:
_instance=None
_lock=threading.Lock()def __init__(self):
time.sleep(1)
super().__init__()
@classmethoddefinstance(cls):if notcls._instance:
with SingleType._lock:if notcls._instance:
cls._instance=SingleType()returncls._instancedeftask():
obj=SingleType.instance()print(obj)for i in range(10):
t= threading.Thread(target=task).start()
加锁后我们看运行结果:
这样就成功解决在多线程环境下单例的问题了, 至于问什么在最外层还要加一个if判断,是为了提高程序的性能。
二、装饰器模式实现
这种实现方式在我上一片将装饰器一文中是有提及的,在这我们再实现一次
defdecorator(cls):def wrap(*args, **kwargs):if notcls._instance:
cls._instance=cls()returncls._instancereturnwrap
@decoratorclassSingleType:
_instance=Nonedef __init__(self):
super().__init__()for i in range(10):
obj=SingleType()print(obj)
当然这里也会出现多线程创建多个实例的问题,可以参照上一个例子自行去实现多线程的版本。
三、使用__new__方法实现
在说__new__方法实现之前,我们需要有个前提知识,我们对象在实例话时会调用__init__方法,但是在调用__init__方法之前,类会先调用__new__方法,__new__方法中决定实例化怎样的对象(可以是调用object的__new__方法正常接着实例话,可以生成其他类的实例等)具体知识可以自行去补充,本文主要讲单例模式。
classSingleType:
_instance=Nonedef __new__(cls, *args, **kwargs):if notcls._instance:
cls._instance= super().__new__(cls)returncls._instancedef __init__(self):
super().__init__()for i in range(10):
obj=SingleType()print(obj)
查看运行结果:
四、使用元类实现单例模式
前提知识: 实例化对象时方法调用顺序是: type类的__cal__方法,其中会调用__new__方法和__init__方法
importthreadingclassSingleType(type):
lock=threading.Lock()def __call__(cls, *args, **kwargs):if notcls._instance:
with SingleType.lock:if notcls._instance:
cls._instance= super().__call__(*args, **kwargs) # 这个地方也可以替换成 obj = cls.__new__(cls) --> cls.__init__(onj) --> cls._instance=objreturncls._instanceclass Test(metaclass=SingleType):
_instance=Nonefor i in range(10):
obj=Test()print(obj)
在代码注释部分的作用相当于自己实现type.__call__的功能。
至此python实现单例设计模式所有的实现方式已经讲完,既然说的是设计模式就要把设计模式的八大准则贴出来,时常警醒自己。
1、依赖倒置原则(DIP):高层模块不依赖于低层模块,而是都依赖于抽象;抽象不依赖于实现细节,实现细节应该依赖于抽象。
2、开放封闭原则(OCP):对扩展开放,对依赖关闭;类的模块是可扩展的,但是不可修改。
3、单一职责原则(SRP):一个类仅有一个能引起他的变化,变化的方向隐含着类的责任。
4、LisKov替换原则:派这类能够替换他的基类,继承表达了类的抽象。
5、接口隔离原则(ISP):不应该强迫客户程序依赖他们不用的方法,接口应该小而强大。
6、优先使用对象组合,而不是类继承:类的继承破坏了封装性,子类父类耦合度高,类继承通常称为“白箱复用和“,对象组合称为”黑箱复用”。并且对象组合只需要有良好的定义的接口,且耦合度低。
7、封装变化点:使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧修改,而不会对另一侧产生不良影响。
8、针对接口编程,而不是针对实现编程:减少系统中个部分的依赖关系,从而实现”高内聚,松耦合“的类型设计方案;不将变量类型声明为某个特定的具体类,而是声明为某个接口。