单例模式(Singleton Pattern):属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。(注意:1、单例类只能有一个实例
。2、单例类必须自己创建自己的唯一实例
。3、单例类必须给所有其他对象提供这一实例
。)
意图: 保证一个类仅有一个实例,并提供一个访问它的全局访问点
。
主要解决: 一个全局使用的类频繁地创建与销毁。
何时使用: 当您想控制实例数目,节省系统资源的时候。
如何解决: 判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码: 构造函数是私有的。
优点: 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
;避免对资源的多重占用。
缺点: 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
注意事项: getInstance() 方法中需要使用同步锁,防止多线程同时进入造成 instance 被多次实例化。
应用实例:水果工厂最近弄到了一个牛批的火龙果,它是一个极为牛批的火龙果,会动,你敢信?!!!∑(゚Д゚ノ)ノ,如下图所示。为了更好的管理这个牛批的火龙果,专门为该火龙果设计了一个单例模式类,对它的重量、售价、产地、保质日期、等数据信息进行单独的管理
。
那我们来一起使用单例模式,实现一个牛批的火龙果的管理类吧!(づ。◕ᴗᴗ◕。)づ
实现的思路:
项目的UML用例图如下:
实现的代码如下:
class Pitaya:
def __init__(self):
# 成员属性为内置,不对外开放对象创建的接口
pass
"""
classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,
但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,
类的方法,实例化对象等。
"""
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(Pitaya, "_instance"):
# hasattr() 函数用于判断对象是否包含对应的属性。
Pitaya._instance = Pitaya(*args, **kwargs)
Pitaya.weight = 21
Pitaya.place = "Canaveria"
Pitaya.price = 20000
Pitaya.date = "2021-12-12"
return Pitaya._instance
def show(obj):
print("重量:%d(kg)\n产地:%s\n售价:%d(元)\n保质日期:%s\n创建信息:%s\n" % (
obj.weight, obj.place, obj.price, obj.date, obj._instance))
if __name__ == '__main__':
# 创建火龙果对象
obj = Pitaya().get_instance()
show(obj)
使用上述方法的原理为:当创建第一个火龙果对象的时候,设定属性_instance,后面再创建新的对象的时候,已经存在 _instance属性就不会再创建新的火龙果对象。
但是,按照以上方式创建的单例,当存在多个管理者同时访问该对象的时候,可能会出现并发异常
。
于是,为了 解决支持多线程访问的问题,对创建对象的部分加锁,优化后的代码如下:
import threading
class Pitaya(object):
_instance_lock = threading.Lock()
def __init__(self):
# 成员属性为内置,不对外开放对象创建的接口
pass
@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(Pitaya, "_instance"):
with Pitaya._instance_lock:
if not hasattr(Pitaya, "_instance"):
# hasattr() 函数用于判断对象是否包含对应的属性。
Pitaya._instance = Pitaya(*args, **kwargs)
Pitaya.weight = 21
Pitaya.place = "Canaveria"
Pitaya.price = 20000
Pitaya.date = "2021-12-12"
return Pitaya._instance
def show(obj):
print("重量:%d(kg)\n产地:%s\n售价:%d(元)\n保质日期:%s\n创建信息:%s\n" % (
obj.weight, obj.place, obj.price, obj.date, obj._instance))
def task(arg):
obj = Pitaya.instance()
show(obj)
if __name__ == '__main__':
for i in range(5):
t = threading.Thread(target=task, args=[i, ])
t.start()
相关的测试用例:
可以通过上述的创建信息发现,我们访问的这些火龙果对象均为同一个对象
,所以该单例模式的实现成功!
本文关于设计模式的讲解思想,参考链接:单例模式
关于@classmethod的用法,参考链接:python中的@classmethod的作用
更多的单例模式的实现方式,参考链接:Python中的单例模式的几种实现方式的及优化
如果有对装饰器的用法还不熟悉的小伙伴,参考这里的讲解:Python小技巧:装饰器(Decorator)
往期推荐: 点这里->Python:水果与设计模式-工厂模式
往期推荐: 点这里->Python:水果与设计模式-抽象工厂模式