1:概念:
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
2:单例模式的优点和缺点:
优点:
1、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
2、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;
3、单例可长驻内存,减少系统开销。
缺点:
1、单例模式的扩展是比较困难的;
2、赋于了单例以太多的职责,某种程度上违反单一职责原则;
3、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试;
4、单例模式在某种情况下会导致“资源瓶颈”。
3:经典的单例案例:
class Singleton(object):
# 重写父类的new方法实现单例设计模式
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
# 创建一个对象
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
if __name__ == '__main__':
s1 = Singleton()
s2 = Singleton()
print(id(s1)) # 1218109700864
print(id(s2)) # 1218109700864
代码解析:
1:在创建一个python对象的时候,本质是调用__new__方法进行创建,这个方法在__init__方法之前调用。
2:思路:
2.1:当第一次创建Singleton实例的时候,我们检查Singleton类是否有_instance这个类属性,如果没有这个类属性,则表示第一次创建Singleton这个类的属性,那么先增加一个类属性,再创建实例对象,并返回。
2.2:当第二次创建Singleton的实例的时候,我们发现已经存在了 ,则直接返回上次创建的单例对象。注意点:
相当于我在类属性中记录这个类的对象,第一次创建,没有这个类属性,我就创建类属性保存这个对象,下次来了之间返回这个类属性(实例对象)。
4:单例的装杯写法—装饰器写法:
问题1:我能不能封装一个装饰器,让装饰器去装饰哪个类,哪个类就变成单例了呢?
问题2:假如我连续创建1000个同一个类的对象,我怎么才能保证用这个类只能创建一个对象呢?
<一>: 分析下面代码的装饰过程:
给Foo函数加上@singleton装饰等价于Foo = singleton(Foo)
1:将Foo函数传入singleton,也就是Foo和cls同时指向内存中同一块地址。
2:由于装饰器外层函数返回内层函数,Foo函数的内存指向变成了装饰器的内层函数。
3:当foo1 = Foo(),本质是调用了装饰器的内层函数,会返回一个cls的对象。
4:此时的cls指向的是Foo函数,所以就会返回Foo函数的对象。
<二>: 问题:调用Foo()本质是调用了wrapper,那么cls不在wrapper中,为什么cls还可以用呢?
原因:装饰器本身就是一个闭包,由于cls在装饰器内部使用到了,导致他的引用计数+1,所以装饰完成后,cls不会被销毁,我在调用的时候任然可以使用。
<三>: 为什么要加锁?加锁的目的是什么?
问题:在并发条件下, 同一个类创建多个对象的时候,用不同的线程创建, 可能一个线程创建了对象但是还没有加入字典的时候,另一个线程又创建了这个类的对象?然后就向字典中加入了对象?这样同一个类创建了两个对象就不是我们想要的单例了。
<四>: 单例效果是如何实现的?
先建立一个空字典,字典的键是类名,值是这个类创建的对象。每次创建对象时,都根据这个类名,检测是否字典中存在了这个类的对象,如果没有创建就创建对象,添加进字典中,然后返回字典中的对象。如果已经存在了,则之间返回字典中的对象。
from threading import Lock
def singleton(cls):
# 字典
instances = {}
# 锁对象
instances_lock = Lock()
def wrapper(*args, **kwargs):
if cls not in instances:
# 使用锁保证只创建一个对象
with instances_lock:
# {"类名": 对象}
# {"Foo": Foo()}
instances[cls] = cls(*args, **kwargs)
# 下一次直接返回对象
return instances[cls]
return wrapper
# 装饰完毕后这个类就是一个单列
@singleton # singleton(Foo)
class Foo(object):
pass
foo1 = Foo()
foo2 = Foo()
5:项目中运用装饰器:
业务需求:当我们在使用云通讯发送短信的时候,我们创建了一个专门和云通讯进行认证的方法,然后另一个方法调用这个方法发送短信。由于每次发送短信,都必须和云通讯认证一次,而认证是个大量的耗时操作。如何优化呢?
答案:将认证的方法封装成一个单例,那么只需要第一次创建的时候跟云通讯进行认证,之后其他用户再来访问,我们不再创建单例,而是直接调用单例,跟云通讯直接通讯。
效果:
1:用户等待的耗时减少,增加用户的体验效果。
2:占用的内存减少,因为整个过程,只有一个CCP对象。
原始代码
def sendTemplateSMS(to, datas, tempId):
# # 初始化REST SDK
# # 权限校验
# # 需要和云通信后台进行网络通讯---耗时
# rest = REST(serverIP, serverPort, softVersion)
# rest.setAccount(accountSid, accountToken)
# rest.setAppId(appId)
#
# # 发短信
# result = rest.sendTemplateSMS(to, datas, tempId)
# for k, v in result.iteritems():
#
# if k == 'templateSMS':
# for k, s in v.iteritems():
# print '%s:%s' % (k, s)
# else:
# print '%s:%s' % (k, v)
使用单例后的代码:
class CCP(object):
"""发送短信的辅助类"""
def __new__(cls, *args, **kwargs):
# 判断是否存在类属性_instance,_instance是类CCP的唯一对象,即单例
if not hasattr(CCP, "_instance"):
# 将客户端和云通信的权限鉴定操作封装到单列中提高性能
# 父类初始化给对象赋值
cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs)
# 权限认证的耗时操作封装到单列只会执行一次【提高性能】
cls._instance.rest = REST(_serverIP, _serverPort, _softVersion)
cls._instance.rest.setAccount(_accountSid, _accountToken)
cls._instance.rest.setAppId(_appId)
# 当CCP类身上有_instance属性,直接返回
return cls._instance
# CCP().send_template_sms()
# CCP().send_template_sms()
# CCP().send_template_sms()
def send_template_sms(self, to, datas, temp_id):
"""发送模板短信"""
# @param to 手机号码
# @param datas 内容数据 格式为数组 例如:{'6位短信验证码值:123456', '5'},如不需替换请填 ''
# @param temp_id 模板Id
result = self.rest.sendTemplateSMS(to, datas, temp_id)
print(result)
# 如果云通讯发送短信成功,返回的字典数据result中statuCode字段的值为"000000"
if result.get("statusCode") == "000000":
# 返回0 表示发送短信成功
return 0
else:
# 返回-1 表示发送失败
return -1
浙公网安备 33010602011771号