飞行的猪哼哼

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

posted on 2020-11-01 18:27  飞行的猪哼哼  阅读(65)  评论(0)    收藏  举报