【设计模式】单例模式

前言

单例模式保证了在程序的不同位置都可以且仅可以取到同一个对象实例:如果实例不存在,会创建一个实例;如果已存在就会返回这个实例。因为单例是一个类,所以你也可以为其提供相应的操作方法,以便于对这个实例进行管理。

应用场景

  • Python的logger就是一个单例模式,用以日志记录
  • Windows的资源管理器是一个单例模式
  • 线程池,数据库连接池等资源池一般也用单例模式
  • 网站计数器

实现方式

  • 使用函数装饰器实现单例
  • 使用类装饰器实现单例
  • 使用 new 关键字实现单例
  • 使用 metaclass 实现单例

函数装饰器实现单例

python 复制代码
def singleton(cls):
    _instance = {}

    def inner():
        if cls not in _instance:
            _instance[cls] = cls()
        return _instance[cls]
    return inner
    
@singleton
class Cls(object):
    def __init__(self):
        pass

cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))

类装饰器实现单例

python 复制代码
class Singleton(object):
    def __init__(self, cls):
        self._cls = cls
        self._instance = {}
    def __call__(self):
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls()
        return self._instance[self._cls]

@Singleton
class Cls2(object):
    def __init__(self):
        pass

cls1 = Cls2()
cls2 = Cls2()
print(id(cls1) == id(cls2))

New、Metaclass 关键字实现单例

python的单例模式__new__()在__init__()之前被调用,用于生产实例对象。

python 复制代码
class Single(object):
    _instance = None
    def __new__(cls, *args, **kw): # 这里不能使用__init__,因为__init__是在instance已经生成以后才去调用的
        if cls._instance is None:
            cls._instance = super(Single, cls).__new__(cls, *args, **kw)
        return cls._instance
    def __init__(self):
        pass

single1 = Single()
single2 = Single()
print(id(single1) == id(single2))

实例

python 复制代码
class Singleton(object):
    __instance = None

    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
            cls.__instance = super(
                Singleton, cls).__new__(cls, *args, **kwargs)
        return cls.__instance

    def __init__(self, status_number):
        self.status_number = status_number


s1 = Singleton(2)
s2 = Singleton(5)
print s1
print s2

print s1.status_number
print s2.status_number

这里我们使用了_init_方法,下面是打印结果,可以看出确实是只有一个实例,共享了实例的变量

python 复制代码
<__main__.Singleton object at 0x7f5116865490>
<__main__.Singleton object at 0x7f5116865490>
5
5

不过这个例子中有一个问题我们没有解决,那就是多线程的问题,**当有多个线程同时去初始化对象时,就很可能同时判断__instance is None,从而进入初始化instance的代码中。**所以为了解决这个问题,我们必须通过同步锁来解决这个问题。

多线程安全单例模式

python 复制代码
# -*- coding: utf-8 -*-
# from MyThread import *
import threading
from threading import Thread

Lock = threading.Lock()


class Singleton(object):

    # 定义静态变量实例
    __instance = None

    def __init__(self):
        pass

    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            try:
                Lock.acquire()
                # double check
                if not cls.__instance:
                    cls.__instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
            finally:
                Lock.release()
        return cls.__instance


def test_singleton_in_thread():
    print (id(Singleton()))

if __name__ == "__main__":
    idx = 0
    while 1:
        Thread(target=test_singleton_in_thread).start()

        idx += 1
        if idx > 0X100:
            break

官方更简单的多线程安全实现

python 复制代码
import threading

def singleton(cls):
    instance = cls()
    instance.__call__ = lambda: instance
    return instance


@singleton
class Highlander:
    x = 100
    # Of course you can have any attributes or methods you like.


def worker():
    hl = Highlander
    hl.x += 1
    print (hl)
    print (hl.x)


def main():
    threads = []
    for _ in range(50):
        t = threading.Thread(target=worker)
        threads.append(t)

    for t in threads:
        t.start()

    for t in threads:
        t.join()


if __name__ == '__main__':
    main()

这样我们确实是解决了多线程数据安全的问题。但是这时候如其它线程要获得Singleton实例的线程还是必须等待,锁的存在降低了效率,有性能损耗

总结, 我自己会有选择性的使用单例模式,把单例模式封装成一个装饰器使用起来很方便。另外对于数据库,尤其是mysql,建议不要使用单例模式。

metaclass 实现单例

在实现单例之前,需要了解使用 type 创造类的方法,代码如下:

python 复制代码
def func(self):
    print("do sth")

Klass = type("Klass", (), {"func": func})

c = Klass()
c.func()

我们使用 type 创造了一个类出来。这里的知识是 mataclass 实现单例的基础。

python 复制代码
class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Cls4(metaclass=Singleton):
    pass

cls1 = Cls4()
cls2 = Cls4()
print(id(cls1) == id(cls2))

跨文件全局变量的使用场景(非单例模式)

单独建立一个文件去保存全局变量来实现

global_var.py

python 复制代码
# -*- coding: utf-8 -*-

def _init():  # 初始化
   global _global_dict
   _global_dict = {}

def set_value(key, value):
   """ 定义一个全局变量 """
   _global_dict[key] = value

def get_value(key, defValue=None):
   """
   获得一个全局变量,不存在则返回默认值
   """
   return _global_dict.get(key, defValue)

master.py

python 复制代码
import time
import global_var as glo

glo._init()
glo.set_value('start', True)

import slave.py


if __name__ == '__main__':
 while 1:
    if glo.get_value('start'):
        print('TTTTTT')
    else:
        print('FFFFFF')
    time.sleep(1)

slave.py

python 复制代码
import threading
import time

import global_var as glo

def fun():
 while 1:
     glo.set_value('start', False)
     print(glo.get_value('start'))
     time.sleep(2)
     glo.set_value('start', True)
     print(glo.get_value('start'))
     time.sleep(2)

t = threading.Thread(target=fun) # 必要
t.start()

执行python master.py

跨文件全局变量的使用场景(单例模式)

singleton.py

python 复制代码
class Singleton:
    def __init__(self):
        self.start = False

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

master.py

python 复制代码
import time
from singleton import Singleton
import slave

if __name__ == '__main__':
    single = Singleton()
    while 1:
        if single.start:
            print('TTTTTT')
        else:
            print('FFFFFF')
        time.sleep(1)

slave.py

python 复制代码
import time
from singleton import Singleton
import threading


def fun():
    single = Singleton()
    while 1:
        single.start = True
        print(single.start)
        time.sleep(2)
        single.start = False
        print(single.start)
        time.sleep(2)


t = threading.Thread(target=fun)
t.start()
复制代码
内容:保证一个类只有一个实例,并提供一个访问它的全局访问点。
适用场景:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
优点:
    对唯一实例的受控访问
    单例相当于全局变量,但防止了命名空间被污染
与单例模式功能相似的概念:全局变量、静态变量(方法)

参考链接:

Python单例模式(Singleton)的N种实现 - 知乎

https://www.cnblogs.com/shenbuer/p/7724091.html

python单例模式及使用场景(跨文件全局变量)_python3 单例变量-CSDN博客

https://www.cnblogs.com/suwings/p/6358061.html

https://www.cnblogs.com/panlq/p/12355917.html

高并发下线程安全的单例模式(最全最经典)_单例模式 并发的情况安全吗-CSDN博客

设计模式(Python)-单例模式 - 简书

python多线程下保持单例模式的实例唯一 -- 峰云就她了

PythonDecoratorLibrary - Python Wiki

https://www.cnblogs.com/kakaliush/p/5228165.html

相关推荐
WaaTong6 小时前
《重学Java设计模式》之 单例模式
java·单例模式·设计模式
WaaTong8 小时前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
霁月风8 小时前
设计模式——观察者模式
c++·观察者模式·设计模式
暗黑起源喵11 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
wrx繁星点点18 小时前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
金池尽干20 小时前
设计模式之——观察者模式
观察者模式·设计模式
也无晴也无风雨20 小时前
代码中的设计模式-策略模式
设计模式·bash·策略模式
捕鲸叉1 天前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
wrx繁星点点1 天前
享元模式:高效管理共享对象的设计模式
java·开发语言·spring·设计模式·maven·intellij-idea·享元模式
凉辰1 天前
设计模式 策略模式 场景Vue (技术提升)
vue.js·设计模式·策略模式