单例模式
模式动机
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。
一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。1
模式定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。2
模式结构
类图3:
实现例子
以下是几种实现单例模式的例子4
1. 使用函数装饰器实现单例
(闭包方式)
这个实现单例模式的方式将原来类的定义隐藏在闭包函数中,通过闭包函数及其中引用的自由变量来控制类对象的生成。由于唯一的实例存放在自由变量中,而且自由变量是无法直接在脚本层进行访问的。这种方式非常隐蔽的保护实例不被修改,因此很适合用于单例模式。
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
if __name__ == '__main__':
instance1 = Cls()
instance2 = Cls()
print(instance1 is instance2) # true
2. 使用类装饰器实现单例
(类作为装饰器之__call__方式)
类作为装饰器要想达到相同的效果只需要将类的对象返回,并且其对象是可以调用的。这是上面这个例子表达的一个核心思想。
这种方式写法很多,也很灵活,其思想基本上就是对被包裹对象的调用实际上调用的是类对象的__call__函数,该函数实际上是对被装饰对象的一次封装。
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 Cls(object):
def __init__(self):
pass
if __name__ == '__main__':
instance1 = Cls()
instance2 = Cls()
print(instance1 is instance2) # true
3. 使用 metaclass 实现单例模式
(元类方式) 5
class Singleton(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
else:
return self.__instance
class Cls(metaclass=Singleton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if __name__ == '__main__':
instance1 = Cls()
instance2 = Cls()
print(instance1 is instance2)
4. 重写创建实例的__new__方法实现单例 (推荐使用,方便)
知识点:
- 一个对象的实例化过程是先执行类的__new__方法,如果我们没有写,默认会调用object的__new__方法,返回一个实例化对象,然后再调用__init__方法,对这个对象进行初始化,我们可以根据这个实现单例.
- 在一个类的__new__方法中先判断是不是存在实例,如果存在实例,就直接返回,如果不存在实例就创建.
class Singleton(object):
# 重写创建实例的__new__方法实现单例
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls.__instance
if __name__ == '__main__':
instance1 = Singleton()
Singleton.__instance = None
instance2 = Singleton()
print(instance1 is instance2) # true
线程安全
import threading
class Singleton(object):
__instance_lock = threading.Lock()
# 重写创建实例的__new__方法实现单例
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
with cls.__instance_lock:
if cls.__instance is None:
cls.__instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls.__instance
模式优缺点
优点1
- 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
缺点
-
由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
-
单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
-
滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。
-
《游戏编程模式》作者说如何规避单例模式6
题外话
- 其他例子
class CSingleTon(object):
"""
单例基类
"""
pMgrObj = None
def __init__(self):
assert (self.pMgrObj is None) or (not isinstance(self.pMgrObj, self))
@classmethod
def Get(cls):
if (cls.pMgrObj is None) or (not isinstance(cls.pMgrObj, cls)):
cls.pMgrObj = cls()
return cls.pMgrObj
sharedManager = Get
@classmethod
def Release(cls):
cls.pMgrObj = None
releaseManager = Release
class Cls(CSingleTon):
pass
if __name__ == '__main__':
instance1 = Cls.Get()
instance2 = Cls.Get()
print(instance1 is instance2)
-
单例与全局变量比较
全局变量可以提供一个实例并提供全局访问,但不能确保只有一个实例。全局变量也会变相鼓励开发人员,用许多全局变量指向许多小对象来造成命名空间的污染。单件不鼓励这样的现象,单单件仍然可能被滥用 -
饿汉模式、懒汉模式