python设计模式之单例模式

无论是在python代码中,还是面试中单例设计模式都是经常被问到和使用的,例如面试中会让你用代码实现单例模式分几种不同的方式,或者问你在平常工作中哪些地方有用到单例设计模式,然后深入探讨。

在本文中我将针对这两个问题来回答和用python代码来编写我们的单例模式。

首先,我们要了解什么是单例模式--官方解释是:确保一个类只有一个实例(也就是类的对象),并且提供一个全局的访问点(外部通过这个访问点来访问该类的唯一实例)。通俗的说就是,无论类实例化多少次,都只有一个相同的对象产生,并且可以通过一个具柄去访问这个这个唯一实例。

 

其次,我们平时代码中碰到是用单例模式设计的代码有哪些呢?

  1. 应用框架的配置文件config(如flask,django框架的配置文件)
  2. 线程池,数据库连接池的创建
  3. 应用程序的日志应用

 

这些应用都有一个共性那就是:他们都是全局共享的对象不会发生变化,不希望重复创建,这样有利于节省空间和减少性能消耗。

 

python实现单例模式的几种方式分别有:

  1. 通过类方法实现
  2. 通过装饰器实现
  3. 通过__new__方法实现
  4. 通过元类实现

 

一、类方法实现

class SingleType:
    _instance = None

    @classmethod
    def instance(cls):
        if not cls._instance:
            cls._instance = SingleType()
        return cls._instance

for 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个对象,这就违背了我们的单例原则了,那怎么解决它呢,答案是加锁。

import threading
import time


class SingleType:
    _instance = None
    _lock = threading.Lock()

    def __init__(self):
        time.sleep(1)
        super().__init__()

    @classmethod
    def instance(cls):
        if not cls._instance:
            with SingleType._lock:
                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()

加锁后我们看运行结果:

 

 这样就成功解决在多线程环境下单例的问题了, 至于问什么在最外层还要加一个if判断,是为了提高程序的性能。

 

二、装饰器模式实现

这种实现方式在我上一片将装饰器一文中是有提及的,在这我们再实现一次

def decorator(cls):
    def wrap(*args, **kwargs):
        if not cls._instance:
            cls._instance = cls()
        return cls._instance
    return wrap


@decorator
class SingleType:
    _instance = None

    def __init__(self):
        super().__init__()

for i in range(10):
    obj = SingleType()
    print(obj)

当然这里也会出现多线程创建多个实例的问题,可以参照上一个例子自行去实现多线程的版本。

 

三、使用__new__方法实现

在说__new__方法实现之前,我们需要有个前提知识,我们对象在实例话时会调用__init__方法,但是在调用__init__方法之前,类会先调用__new__方法,__new__方法中决定实例化怎样的对象(可以是调用object的__new__方法正常接着实例话,可以生成其他类的实例等)具体知识可以自行去补充,本文主要讲单例模式。

class SingleType:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        super().__init__()


for i in range(10):
    obj = SingleType()
    print(obj)

查看运行结果:

 

 

四、使用元类实现单例模式

前提知识: 实例化对象时方法调用顺序是: type类的__cal__方法,其中会调用__new__方法和__init__方法

import threading


class SingleType(type):
    lock = threading.Lock()

    def __call__(cls, *args, **kwargs):
        if not cls._instance:
            with SingleType.lock:
                if not cls._instance:
                    cls._instance = super().__call__(*args, **kwargs)        # 这个地方也可以替换成 obj = cls.__new__(cls) --> cls.__init__(onj) --> cls._instance=obj
        return cls._instance


class Test(metaclass=SingleType):
    _instance = None


for i in range(10):
    obj = Test()
    print(obj)

在代码注释部分的作用相当于自己实现type.__call__的功能。

 

至此python实现单例设计模式所有的实现方式已经讲完,既然说的是设计模式就要把设计模式的八大准则贴出来,时常警醒自己。

1、依赖倒置原则(DIP):高层模块不依赖于低层模块,而是都依赖于抽象;抽象不依赖于实现细节,实现细节应该依赖于抽象。
 
2、开放封闭原则(OCP):对扩展开放,对依赖关闭;类的模块是可扩展的,但是不可修改。
 
3、单一职责原则(SRP):一个类仅有一个能引起他的变化,变化的方向隐含着类的责任。
 
4、LisKov替换原则:派这类能够替换他的基类,继承表达了类的抽象。
 
5、接口隔离原则(ISP):不应该强迫客户程序依赖他们不用的方法,接口应该小而强大。
 
6、优先使用对象组合,而不是类继承:类的继承破坏了封装性,子类父类耦合度高,类继承通常称为“白箱复用和“,对象组合称为”黑箱复用”。并且对象组合只需要有良好的定义的接口,且耦合度低。
 
7、封装变化点:使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧修改,而不会对另一侧产生不良影响。
 
8、针对接口编程,而不是针对实现编程:减少系统中个部分的依赖关系,从而实现”高内聚,松耦合“的类型设计方案;不将变量类型声明为某个特定的具体类,而是声明为某个接口。 

 

posted @ 2020-06-24 15:45  种树飞  阅读(801)  评论(0编辑  收藏  举报