1.单例模式(Singleton)

单例,顾名思义是一个实例,即在一个项目之中,单例的类只实例化一次。它常常应用于数据库操作、日志函数。

在一个大型项目中使用到日志和数据库操作的地方很多,不能每个文件都去单独实例化一次,此时单例模式就显示出了他的价值。

单例的核心在类的内部方法 __new__(),每次实例化都是通过执行new函数来返回实例对象。

单例就是在类里面定义一个作用域最高的标志性的属性,如果实例化过一次,那这个属性为True,否则为False,那么返回上次实例化的对象。

意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用性

当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。

当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

单例模式的优点和应用

单例模式的优点:
1、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
2、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;
3、单例可长驻内存,减少系统开销。
单例模式的应用举例:
1、生成全局惟一的序列号;
2、访问全局复用的惟一资源,如磁盘、总线等;
3、单个对象占用的资源过多,如数据库等;
4、系统全局统一管理,如Windows下的Task Manager;
5、网站计数器。

单例模式的缺点

1、单例模式的扩展是比较困难的;
2、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到);
3、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试;
4、单例模式在某种情况下会导致“资源瓶颈”。

核心代码

class Singleton(object):

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            org = super(Singleton, cls) # 让cls继承指定的父类 Singleton
            cls._instance = org.__new__(cls) # 创建实例
        return cls._instance # 返回具体的实例

# 复写内部方法__new__()
# 通过hasattr函数判断该类实例化时有没有_instance属性
# 如果不存在,那么继承并返回原始的__new__方法给_instance属性
# 如果存在则直接返回_instance属性所指的对象

示例代码1

class Singleton(object):
    
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            org = super(Singleton, cls)
            cls._instance = org.__new__(cls)
        return cls._instance
 
class MyClass(Singleton):
    def __init__(self, name):
        self.name = name
 
if __name__ == "__main__":
    a = MyClass("A")
    print(a.name)
    b = MyClass("B")
    print(a.name)
    print(b.name)
    b.name = 'C'
    print(a.name)
    print(b.name)


输出结果:
A
B
B
C
C

示例代码2

class Singleton(object):
 
    def __init__(self):
        print( "__init__" )
 
    def __new__(cls, *args, **kwargs):
        print( "__new__" )
        if not hasattr(Singleton, "_instance" ):
            print( " 创建新实例 " )
            org = super(Singleton, cls)
            Singleton._instance = org.__new__(cls)
        return Singleton._instance
 
 
obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)


输出结果:
__new__
 创建新实例
__init__
__new__
__init__
<__main__.Singleton object at 0x7fc871f65da0> <__main__.Singleton object at 0x7fc871f65da0>

但这样其实有个小问题,看输出其实执行了两遍__init__()方法,既然是同一个对象,初始化两次,这是不太合理的,我们可以改造一下:

class Singleton(object):
 
    def __init__(self):
        if not hasattr(Singleton, "_first_init"):
            print("__init__")
            Singleton._first_init = True
 
    def __new__(cls, *args, **kwargs):
        print("__new__")
        if not hasattr(Singleton, "_instance"):
            print( " 创建新实例 " )
            org = super(Singleton, cls)
            Singleton._instance = org.__new__(cls)
        return Singleton._instance
 
 
obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)


输出结果:
__new__
 创建新实例
__init__
__new__
<__main__.Singleton object at 0x7fc5f8d57da0> <__main__.Singleton object at 0x7fc5f8d57da0>

而且__new__()方法是支持多线程的,不需要单独再加线程锁进行规避操作

装饰器实现单例

def Singleton(cls):
	_instance={} # 字典用来保存被装饰类的实例对象
	def _singleton(*args,**kwagrs):
		if cls not in  _instance:
			_instance[cls]=cls(*args,**kwagrs)
		return _instance[cls]
	return _singleton

@Singleton
class A:
	a=1
	def __init__(self,x=0):
		self.x=x
	
a1=A(2)
a2=A(4)
print(id(a1), id(a2))

类装饰器方式

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 B(object):
    def __init__(self):
        pass
 
b1 = B()
b2 = B()
print(id(b1), id(b2))

类方法实现

class Singleton(object):
    def __init__(self, *args, **kwargs):
        pass
        
    @classmethod
    def get_instance(cls, *args, **kwargs):
        # hasattr() 函数用于判断对象是否包含对应的属性 , 这里是看看这个类有没有 _instance 属性  
        if not hasattr(Singleton, '_instance' ):
            Singleton._instance = Singleton(*args, **kwargs)
 
        return Singleton._instance
 
 
s1 = Singleton()  # 使用这种方式创建实例的时候 , 并不能保证单例 
s2 = Singleton.get_instance()  # 只有使用这种方式创建的时候才可以实现单例 
s3 = Singleton()
s4 = Singleton.get_instance()
 
print(id(s1), id(s2), id(s3), id(s4))

其实这种方式的思路就是,调用类的get_instance()方法去创建对象,get_instance方法会判断之前有没有创建过对象,有的话也是会返回之前已经创建的对象,不再新创建,但是这样有一个弊端,就是在使用类创建s3 = Singleton()这种方式的时候,就不能保证单例了,也就是说在创建类的时候一定要用类里面规定的get_instance()方法创建。 再者,当使用多线程时这样也会存在问题,我们来看下面的代码:

import threading
class Singleton(object):
    def __init__(self, *args, **kwargs):
        import time
        time.sleep(1)
 
    @classmethod
    def get_instance(cls, *args, **kwargs):
        # hasattr() 函数用于判断对象是否包含对应的属性 , 这里是看看这个类有没有 _instance 属性
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)
 
        return Singleton._instance
 
 
def task():
    obj = Singleton.get_instance()
    print(obj)
 
 
for i in range(10):
    t = threading.Thread(target=task)
    t.start()

程序执行后,打印结果:

<__main__.Singleton object at 0x031014B0>
<__main__.Singleton object at 0x00DA32F0>
<__main__.Singleton object at 0x03101430>
<__main__.Singleton object at 0x03101530>
<__main__.Singleton object at 0x031015B0>
<__main__.Singleton object at 0x031016B0>
<__main__.Singleton object at 0x03101630>
<__main__.Singleton object at 0x03101830>
<__main__.Singleton object at 0x03101730>
<__main__.Singleton object at 0x031017B0>
 
Process finished with exit code 0

如果在__init__()方法方法中有一些IO操作(此处使用time.sleep(1)来模拟),就会发现此时并不是同一个实例对象,这是因为在一个对象创建的过程中,会先去获取_instance属性,判断之前有没有实例对象,因为IO耗时操作,当他们判断的时候,还没有对象完成实例化,所以就会调用init()方法进行实例化,结果就是调用了多次,然后就创建了多个对象。那要如何解决呢? 答案是加锁,在获取对象属性_instance的时候加锁,如果已经有人在获取对象了,其他的人如果要获取这个对象,就先等一下,因为前面的那个人,可能在创建对象,就是还没创建完成。 代码如下:

import threading
class Singleton(object):
    _instance_lock = threading.Lock()  # 线程锁
    
    def __init__(self, *args, **kwargs):
        import time
        time.sleep(1)
 
    @classmethod
    def get_instance(cls, *args, **kwargs):
        with Singleton._instance_lock:
            # hasattr() 函数用于判断对象是否包含对应的属性 , 这里是看看这个类有没有 _instance 属性
            if not hasattr(Singleton, '_instance'):
                Singleton._instance = Singleton(*args, **kwargs)
 
            return Singleton._instance

但是为了保证线程安全,在类内部加入锁机制,又会使加锁部分代码串行执行,速度降低。

posted @ 2022-08-26 15:45  阿木古冷  阅读(250)  评论(0编辑  收藏  举报