python 设计模式之单例模式

单例模式

单例模式是一种创建型模式,它的核心要求是一个类只有一个实例对象,这个要求是违反直觉的,因为在学习面向对象时,所有教程都告诉你,一个类可以创建出多个实例对象。

之所以提这样的要求,是因为在实践中,需要对一些资源的访问做限制,比如数据库连接。假设你实现了一个专门负责连接mysql进行操作的类,它维护了一个大小为10的连接池。在程序的其他地方,如果允许这个类创建出多个实例对象,那么每创建出一个实例对象,都要维护一个大小为10的连接池,那么你的程序与mysql之间所建立的连接就会越来越多,这显然会导致异常灾难。

在单例模式一下,一个类只能创建出一个实例,不管你怎样使用类的构造函数,都永远只有一个实例,这样就能对那些敏感的资源做访问限制。

2. python实现单例模式

python 有很多种实现单例模式的方法,有简单的,有复杂的,我们先来看看最简单的实现方式

2.1 利用模块实现

编写脚本single.py

class DbSingleton():
    def __init__(self, host, port, username, password):
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.pool = None        # 连接池

    def connect(self):
        print("建立连接")


db_singleton = DbSingleton()

在single.py 脚本中,我实现了一个非常普通的类DbSingleton, 并在脚本里创建实例对象db_singleton, 在其他的模块里,如果需要使用数据库连接,只需要将db_singleton 导入即可

from single import db_singleton

在任何模块里,不论这个导入的过程被执行了对少次,都永远只有一个db_singleton, 这就是python中实现单例模式的最简单的方法,它简单到让你质疑:如果直接使用DbSingleton 创建一个新的对象,不就等于是破坏了单例模式了么?

没错,如果你想破坏单例模式,总能够想到办法,使用设计模式的初衷一部分是为了防止开发人员犯错,但更重要的是让系统有良好的架构,便于维护和升级,你硬是要想办法破坏一种设计模式,那你总能找到办法。

2.2 利用元类

既然受到质疑,那么我们就实现一个不容易破坏的单例模式

from threading import RLock

class SingletonType(type):
    single_lock = RLock()

    def __call__(cls, *args, **kwargs):   # 创建cls的对象时候调用
        with SingletonType.single_lock:
            if not hasattr(cls, "_instance"):
                cls._instance = super(SingletonType, cls).__call__(*args, **kwargs)     # 创建cls的对象

        return cls._instance


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


single_1 = Singleton('第1次创建')
single_2 = Singleton('第2次创建')

print(single_1.name, single_2.name)     # 第1次创建 第1次创建
print(single_1 is single_2)   

在第一次创建Singleton的实例对象时,Singleton 还没有_instance属性,待实例对象被创建好以后,将这个唯一的示例对象赋值给cls._instance,其后再创建新的示例对象时,Singleton 已经有了_instance 属性,便不会创建新的实例对象了。

我使用元类来实现单例模式,元类是专门用来创建我们自定义类的类,就上面这段代码而言,类Singleton 是 SingletonType 的实例。

当语句 Singleton('第1次创建') 被执行时,则是在调用执行SingletonType 的 __call__方法,我在这里做一些手脚,就能确保永远只有一个Singleton 实例对象被创建。

这个单例模式中,我还考虑到了多线程条件下单例模式的应用问题,在创建实例对象时特地加了锁,这样更加万无一失。

除了利用元类,还可以利用装饰器,具体方法可以参考的我另一篇文章python实现单例模式的5种方法

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案