python设计模式--单例模式

一、什么是单例模式

单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。

许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。
比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,
然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

二、单例模式的应用场景

举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。

继续说回收站,我们在实际使用中并不存在需要同时打开两个回收站窗口的必要性。假如我每次创建回收站时都需要消耗大量的资源,而每个回收站之间资源是共享的,那么在没有必要多次重复创建该实例的情况下,创建了多个实例,这样做就会给系统造成不必要的负担,造成资源浪费。

再举一个例子,网站的计数器,一般也是采用单例模式实现,如果你存在多个计数器,每一个用户的访问都刷新计数器的值,这样的话你的实计数的值是难以同步的。但是如果采用单例模式实现就不会存在这样的问题,而且还可以避免线程安全问题。同样多线程的线程池的设计一般也是采用单例模式,这是由于线程池需要方便对池中的线程进行控制

同样,对于一些应用程序的日志应用,或者web开发中读取配置文件都适合使用单例模式,如HttpApplication 就是单例的典型应用。

从上述的例子中我们可以总结出适合使用单例模式的场景和优缺点:

适用场景:

  • 需要生成唯一序列的环境(特定场景需要唯一对象)
  • 需要频繁实例化然后销毁的对象。(从使用频率的角度)
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。(从资源消耗的角度)
  • 方便资源相互通信的环境

三、单例模式的优缺点

优点:

  • 在内存中只有一个对象,节省内存空间;
  • 避免频繁的创建销毁对象,可以提高性能;
  • 避免对共享资源的多重占用,简化访问;
  • 为整个系统提供一个全局访问点。

缺点:

  • 不适用于变化频繁的对象;
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
  • 如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;

四、创建单例模式的方法

1.不管用什么方式实现单例,其主要逻辑都是判断是否存在已经创建的实例,有就返回已经创建过的实例,没就实例化一个实例并存储起来;

2.单例在单线程模型下,是线程安全的,不管怎么样创建实例,都有且只有一个,而面对多线程任务时,一般的单例代码无法承担多线程任务,当有io延时操作时,会生成id不同的实例,所以如果需要实现多线程单例,那么就在创建实例时增加线程锁;

3.实现单例的方式不止三种,只要你抓住if判断的主要逻辑点,就能实现单例,所以单例的实现方式是多种多样的,这里列举了比较常用的几种;

# 使用模块
# 其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:

class Singleton(object):
    def foo(self):
        pass
singleton = Singleton()
# 将上面的代码保存在文件 mysingleton.py 中,要使用时,直接在其他文件中导入此文件中的对象,这个对象即是单例模式的对象

from mysingleton import singleton   
# 使用装饰器
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(id(a1),id(a2))
# 使用类方法
import time
import threading
class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self):
        pass

    #因为电脑速率执行很快,所以在第一个线程返回Singleton._instance之前都执行了
    #Singleton._instance = Singleton(*args, **kwargs)语句
    #所以多线程模式下会创建不同实例,但是后边在执行会保存最后一个执行的实例
    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            print("想要创建实例")
            with Singleton._instance_lock:
                if not hasattr(Singleton, "_instance"):
                    print("要开始创建了")
                    cls._instance = Singleton(*args, **kwargs)
        return cls._instance
#四、使用__new__
'''
通过上面例子,我们可以知道,当我们实现单例时,为了保证线程安全需要在内部加入锁

我们知道,当我们实例化一个对象时,是先执行了类的__new__方法
(我们没写时,默认调用object.__new__),实例化对象;
然后再执行类的__init__方法,对这个对象进行初始化,所有我们可以基于这个,实现单例模式
'''
# class Singleton:
#     def __new__(cls, *args, **kwargs):
#         if not hasattr(cls,'_instances'):
#             cls._instances = super().__new__(cls)
#         return cls._instances
#
#     def __call__(self, *args, **kwargs):
#         print("我是Singleton")

class MyClass():
    def __init__(self,a):
        print(self._instances)
        self.a = a

    def __new__(cls, *args, **kwargs):
        #返回类的实例,有因为cls._instances = super().__new__(cls)
        #是父类的实例,所以可以直接返回cls._instances
        if not hasattr(cls,'_instances'):
            cls._instances = super().__new__(cls)
        print(cls._instances)
        return cls._instances

    def __call__(self, *args, **kwargs):
        #实现该方法称为可调用对象
        print('我被调用了')

# a = MyClass(10)
# a()
# b = MyClass(20)
# print(a.a)
# print(b.a)
# print(id(a),id(b))

# print(hasattr(a.__new__,'__call__'))
# print(hasattr(a,'__call__'))
# print(hasattr(a.a,'__call__'))
#五、使用元类
import threading

#__new__在创建类只执行一次,所以使用__call__
#因为Foo()其实算是SingletonType的实例,所以会自动执行元类的__call__方法
#obj1 = Foo('name1'), cls()调用会调用__call__方法,而cls()为类的实例,所以__call__应该返回类的实例
#元类中有一个特殊的方法__call__,这个方法会截断类的__new__和__init__方法,阻止其执行
#__call__ 应该返回实例,和类的__new__方法返回的一样(type会调用类的__new__和__init__,super()则不会)。

class SingletonType(type):

    def __new__(cls, name, bases, attrs):
        print("metaclass __new__")
        return type.__new__(cls, name, bases, attrs)


    _instance_lock = threading.Lock()
    def __call__(cls, *args, **kwargs):
        print('Metaclass __call__')
        print(cls, *args, **kwargs)
        if not hasattr(cls, "_instance"):
            with SingletonType._instance_lock:
                if not hasattr(cls, "_instance"):
                    #super已经明确子类,不用加cls,而type直接使用元类创建不明确cls,所以需要加
                    cls._instance = super().__call__(*args, **kwargs)
                    # cls._instance = type.__call__(cls,*args, **kwargs)
        return cls._instance
      
# 不能使用__new__函数实现单例模式,因为返回的只是类而不是实例,类在创建一次之后已经存在内存里,所以第二次创建相同的实例时不会再经过元类对类进行创建,所以需要使用__call__,而__call__所拿到的cls代表着当前类

class Foo(metaclass=SingletonType):
    def __init__(self,name):
        print('cls __init__')
        self.name = name
    def __new__(cls, *args, **kwargs):
        print('cls __new__')
        return super().__new__(cls)


obj1 = Foo('name1')
# obj2 = Foo('name')
# # print(obj1,obj2)
# print(obj1.name)
#
# a = [1,2,3]

posted @ 2021-03-31 15:25  MrSatan  阅读(154)  评论(0编辑  收藏  举报