• 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、针对接口编程,而不是针对实现编程:减少系统中个部分的依赖关系,从而实现”高内聚,松耦合“的类型设计方案;不将变量类型声明为某个特定的具体类,而是声明为某个接口。 
  • 相关阅读:
    雨林木风操作系统有感
    Bitcoin P2P 虚拟货币原理详解
    SVN switch 用法详解
    Bitcoin P2P 货币:有史以来最危险的项目
    回旋线科普
    Mathematica 如何绘制双纵坐标轴的图像?
    c#数据库操作
    在自定义HttpHandler中无法使用Session
    VS2010安装因net framework4.0无法安装而失败的解决方法
    .net framework 4.0 0xc8000247错误解决
  • 原文地址:https://www.cnblogs.com/lifei01/p/13187852.html
Copyright © 2020-2023  润新知