单例模式
前言
单例模式应该是所有接触的设计模式初学者第一个听过的设计模式,这个模式应该是所有设计模式中最简单的一个模式了。值得注意的是,许多开发者将单例模式视为一种反模式,因此单例模式在 Python 中的使用频率现在越来越少了。
反模式(英文:Anti-patterns或pitfalls), 是指用来解决问题的带有共同性的不良方法。它们已经经过研究并分类,以防止日后重蹈覆辙,并能在研发尚未投产时辨认出来。软件开发中公认的反模式
意图
UML 图
应用场景
单例模式最常使用的场景就是连接数据库以及日志等等,单例模式使用的场景通常有以下几个特点
代码
简单单例模式
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
def some_action(self):
pass
if __name__ == "__main__":
s1 = Singleton()
s2 = Singleton()
if id(s1) == id(s2):
print("Singleton works")
else:
print("Singleton failed")
看一下输出:
Singleton works
线程安全单例模式
上面的单例模式可能会在多线程环境中出错,所以为了解决这个问题,我们将单例模式再升级一下
from threading import Lock, Thread
class SingletonMeta(type):
_instances = {}
_lock: Lock = Lock()
def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
value: str = None
def __init__(self, value: str) -> None:
self.value = value
def some_action(self):
pass
def test_singleton(value: str) -> None:
singleton = Singleton(value)
print(singleton.value)
if __name__ == "__main__":
# 由于是单例模式,预期两个线程输出的结果应该一样,都是第一个 FOO
process1 = Thread(target=test_singleton, args=("FOO",))
process2 = Thread(target=test_singleton, args=("BAR",))
process1.start()
process2.start()
看一下输出结果:
FOO
FOO
总结
单例模式优缺点
优点
缺点
- 违反了单一职责原则。
- 单例模式可能掩盖不良设计, 可能会隐藏类之间的依赖关系。
- 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
- 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法。
- 单例不支持有参数的构造函数,比如我们创建一个连接池的单例对象,我们没法通过参数来指定连接池的大小。
本文由 Leetao 创作,采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
关注回复 Python 领取学习大礼包