单例模式的五种设计方案

活在当下

python设计模式之单例模式

单例模式是一种常用的开发设计模式,它的主要目的就是确保只有一个实例对象的存在,也就是说当你希望一个类的功能比较单一,你只需要一个实例对象就可以完成需要的时候,就可以使用单例模式,以此来节省内存资源。

比如我们在web开发项目中,我们经常需要做到的一个功能就是使用单例模式来开发短信验证码这个功能。我们通常是使用通讯产品来发送短信验证码,那么我们就只需要用一个实例对象去完成这个短信发送的功能就可以了。

1. 模块实现单例模式

大家应该都知道,模块的导入只能够让被导入程序执行一次,你多次导入也只会执行一次,那么我们可以说模块就是一个天然的单例模式,因此,我们只需把相关函数和数据定义在一个模块中,使用的时候将它导入到其他模块使用就可以达到单例模式的效果了。

single.py
···
class Singleton(object):
    def foo(self):
        pass
    print(1)
singleton = Singleton()

from singleton import singleton
from singleton import singleton
执行结果:
1 

2. 使用装饰器实现单例模式

装饰器的作用相信大家都是知道的,可以给我们别的函数或者类添加一个功能,那么我们同样的可以给类写一个逻辑,让类只能生成一个实例对象。

def Singleton(cls):
    _instance = {}
    def singleton(*args, **kargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kargs)
        return _instance[cls]
    return singleton

@Singleton
class A(object):
    a = 1
    def __init__(self, x=0):
        self.x = x

a1 = A(2)
a2 = A(3)
print(a1)
print(a2)
执行结果:
<__main__.A object at 0x0000027432DABE48>
<__main__.A object at 0x0000027432DABE48> 

3. 使用类方法实现单例模式

当我们实用类直接创建实例对象的时候,创建的并不是单例对象,那么我们需要在类中定义一个类方法来编辑逻辑实现单例模式,主要思路就是会去判断类是否有_instance这个属性,如果有则直接返回这个实例,没有则创建实例。

class Singleton(object):
    def __init__(self,*args,**kwargs):
        pass

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

s1 = Singleton.get_instance()
s2 = Singleton.get_instance()
print(s1)
print(s2)
执行结果:
<__main__.Singleton object at 0x0000017B0359B550>
<__main__.Singleton object at 0x0000017B0359B550> 

但是这样去实现单例模式有个隐患,当我们加入多线程去创建实例对象的时候,我们的执行速度够快还不会出现影响,当执行速度不够快的时候,一个线程去创建实例然后拿到了_instance这个属性去判断,其他的线程可能也同时会拿到这个_instance这个属性,发现并没有实例存在,所以这两个线程就会同时创建一个实例,就会造成创建了多个实例的现象,我们的单例模式自然也就失效了。举例来说明,我们这里直接加上这个休息时间演示执行慢的情况。

import threading
import time

class Singleton(object):
    def __init__(self, *args, **kwargs):
        time.sleep(1)

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance

def task(arg):
    obj = Singleton.get_instance(arg)
    print(obj)

for i in range(10):
    t = threading.Thread(target=task, args=[i, ])
    t.start()
执行结果:
<__main__.Singleton object at 0x000001774942A518>
<__main__.Singleton object at 0x00000177495FA160>
<__main__.Singleton object at 0x00000177495F1EF0>
<__main__.Singleton object at 0x00000177495E5940>
<__main__.Singleton object at 0x00000177495AC0F0>
<__main__.Singleton object at 0x00000177495E58D0>
<__main__.Singleton object at 0x00000177495A6FD0>
<__main__.Singleton object at 0x00000177495F1D68>
<__main__.Singleton object at 0x00000177496060B8>
<__main__.Singleton object at 0x0000017749606240> 

现在我们是不是就发现了这个实例对象变为了不同的ip了?这个也就是我们刚才所说道的这个原因。那么我们怎么去解决这问题呢?在这里我们可以通过加上线程锁达到效果。我们加了线程锁之后,给这个_instance加上这个锁,让每一个县城用完了之后才释放它,接着下一个线程才能拿到它继续运行程序,就不会再造成同时拿到这个属性值而直接创建了不同的实例的情况。

import threading
import time

class Singleton(object):
    _instance_lock = threading.Lock()
    def __init__(self, *args, **kwargs):
        time.sleep(1)

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(Singleton, '_instance'):
            with Singleton._instance_lock:
                if not hasattr(Singleton, '_instance'):
                    Singleton._instance = Singleton(*args, **kwargs)
        return Singleton._instance

def task(arg):
    obj = Singleton.get_instance(arg)
    print(obj)

for i in range(10):
    t = threading.Thread(target=task, args=[i, ])
    t.start()
执行结果:
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550> 

4. 使用__new__方法实现单例模式

大家应该都知道我们的实例对象就是通过__new__方法创建出来的,如果我们重写类的__new__方法,我们是不是可以直接把这个可以创建多个实例的情况改写为只能创建一个实例对象呢?答案是肯定的,我们可以在重写__new__方法的时候,添加判断如果存在了实例对象,那么直接返回已经创建了的对象即可,这样就可以达到我们的这个效果了。

import threading

class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self, *args, **kwargs):
        pass

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            with Singleton._instance_lock:
                if not hasattr(cls, '_instance'):
                    Singleton._instance = super().__new__(cls)

            return Singleton._instance

def task(arg):
    obj = Singleton()
    print(obj)

for i in range(10):
    t = threading.Thread(target=task, args=[i, ])
    t.start()
运行结果:
<__main__.Singleton object at 0x00000111C88B9588>
None
None
None
None
None
None
None
None
None 

5. 使用元类metaclass实现单例模式

Python的元类是控制类的创建的类,既然如此,那么我们是不是可以再在创建类的时候通过元类来创建我们的类,使得它只能生成一个实例对象呢?

import threading

class SingletonType(type):
    _instance_lock = threading.Lock()
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            with SingletonType._instance_lock:
                if not hasattr(cls, "_instance"):
                    cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
        return cls._instance

class Foo(metaclass=SingletonType):
    def __init__(self,name):
        self.name = name

obj1 = Foo('name')
obj2 = Foo('name')
print(id(obj1))
print(id(obj2))
执行结果:
<__main__.Foo object at 0x000001E97FC14400>
<__main__.Foo object at 0x000001E97FC14400> 

好了,那么这里就是给大家介绍的几种创建单例模式的方法,总的来说,不管有没有用,面试的时候,我们装x还是有作用的,你get到了吗?

阅读 194
12 声望
15 粉丝
0 条评论
12 声望
15 粉丝
宣传栏