单例模式(Singleton Design Pattern)及代码实现

什么是 单例模式?

顾名思义 单例模式 便是指 一个类在一个运行的程序中只能有一个实例;

 

代码中的例子(何时该使用此模式):

  • 程序中 对 某个固定文件的多次读取,可以使用单例模式,这样减少程序IO时间 和 生成新对象的 内存资源占用, 相当于 缓存功能;

 

单例模式 关键的角色:

  1. 单例 角色: 能创建全局唯一一个实例的类

 

该模式的主要优缺点如下:

 

示例代码部分(多个实现方式)

1.通过 metaclass方式实现

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren 
All rights reserved
create time '2020/11/1 14:45'

Usage:
此程序 生成类 和对象的顺序 或 关系为:
在程序 运行 到 class Foo(metaclass=SingletonType) 时 先调用 SingletonType的__new__,__init__ 方法 生成 Foo类
然后在 运行 到 obj = Foo('xx') 时 调用 SingletonType的__new__方法,其内部调用 Foo类的__new__,__init__方法 生成具体的 实例

总结: 先通过 type 生成 用户 类,然后 用户类才能具体实例化,而 生成用户类的过程 对应 type来说 就是对 type进行实例化
"""


class SingletonType(type):
    """
    type 类 作用是 创建类的 一种类,在创建用户定义的类时,会先执行 type类的 __new__,__init__方法(相当于对 type类进行实例化,只是实例化的结果 为用户类) 生成对应用户类
    在 用户类实例化时,会调用 type类的 __call__方法,__call__方法内部 会调用 对应用户类的 __new__,__init__ 方法 完成 创建实例对象和初始化功能
    此 SingletonType 类 继承自 type 类,对其 __call__ 方法进行了修改,使 用户定义的 Foo类 改成了单例模式
    用户定义的Foo类通过 metaclass实现 父类SingletonType 对子类Foo 的控制,而 一般继承关系中,父类是无法控制子类的
    """
    _instance = None

    def __init__(self, *args, **kwargs):
        """
        对实例化后的用户类(实例化的结果 就是 用户类) 初始化时用到
        :param args:
        :param kwargs:
        """
        super(SingletonType, self).__init__(*args, **kwargs)

    def __new__(cls, *args, **kwargs):
        """
        实例化用户类(实例化的结果 就是 用户类)
        :param args:
        :param kwargs:
        """
        return type.__new__(cls, *args, **kwargs)

    def __call__(cls, *args, **kwargs):  # 这里的cls,即Foo类
        """
        在 用户类 实例化为 具体对象时调用
        :param args:
        :param kwargs:
        :return:
        """
        # 判断 实例是否已经存在
        if not cls._instance:
            obj = cls.__new__(cls, *args, **kwargs)
            cls.__init__(obj, *args, **kwargs)  # Foo.__init__(obj)
            cls._instance = obj
        else:
            obj = cls._instance
        return obj


class Foo(
    metaclass=SingletonType):  # 指定创建Foo的type为SingletonType,通过 debug模式 运行到此时 会 通过调用 SingletonType类 的 __new__,__init__方法 创建 Foo的类,所以 用户定义的python类 都是 type这个类的实例
    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        return object.__new__(cls)

    def __str__(self):
        return self.name


obj = Foo('刚开始初始化的对象')
obj1 = Foo('后续生成的对象')
print(id(obj), obj)
print(id(obj1), obj)

2.使用 __new__方法实现

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren 
All rights reserved
create time '2020/10/29 19:52'

Usage:
使用 __new__ 方法实现
"""


class Foo:
    _instance = None

    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        """
        实例化 时 通过 cls._instance 判断是否之前实例化过
        :param cls: 类
        :param args:
        :param kwargs:
        """
        if not cls._instance:
            # 通过object实例化对象
            obj = object.__new__(cls)
            cls._instance = obj
        else:
            obj = cls._instance
        return obj


foo1 = Foo('foo1')
foo2 = Foo('foo2')
print(id(foo1))
print(id(foo2))

3.通过 装饰器 实现

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/10/29 19:52'

Usage:
使用 装饰器 方法实现 单例模式
"""


def singleton(cls):
    """
    单例模式 装饰器
    :param cls:
    :return:
    """

    def _singleton(*args, **kwargs):
        """
        通过 cls._instance 判断是否之前实例化过
        :param args:
        :param kwargs:
        :return:
        """
        if not cls._instance:
            obj = cls(*args, **kwargs)
            cls._instance = obj
        else:
            obj = cls._instance
        return obj

    return _singleton


@singleton
class Foo:
    _instance = None

    def __init__(self, name):
        self.name = name


foo1 = Foo('foo1')
foo2 = Foo('foo2')
print(id(foo1))
print(id(foo2))

4.通过 模块 实现(此方法 在多线程 也是安全的)

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren 
All rights reserved
create time '2020/10/29 19:52'

Usage:
使用 模块 方法实现 单例模式
模块 在第一次导入时 生成 .pyc文件,第二次导入时 直接加载 .pyc文件,不会再执行 模块内的代码
"""


class Foo:
    _instance = None

    def __init__(self):
        print('初始化对象')

    def read_file(self):
        """

        :return:
        """
        print('此处执行 读取文件等操作,此模块 实现单例的 类')


foo = Foo()

调用时

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren 
All rights reserved
create time '2020/11/1 18:14'

Usage:

"""
from design_pattern.model.singleton_model import foo


def ge_foo():
    print('第一次调用 foo')
    foo.read_file()


if __name__ == '__main__':
    ge_foo()

5.使用 类 + 类方法 实现 (不推荐使用,因为需要 调用类方法,多此一举)

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/11/1 14:45'

Usage:
使用 类 实现 单例模式
"""


class Foo:
    _instance = None

    def __init__(self, name):
        """

        :param name:
        """
        self.name = name

    @classmethod
    def instance(cls, name):
        """
        创建对象
        :param args:
        :param kwargs:
        :return:
        """
        if not cls._instance:
            print('新建实例')
            cls._instance = Foo(name)
        return cls._instance

    def __str__(self):
        """

        :return:
        """
        return self.name


obj = Foo.instance('第一次实例化')
print(id(obj), obj)
obj1 = Foo.instance('第二次')
print(id(obj1), obj1)

 

线程安全问题

在 如上所有实例代码中 均 是 在 单线程中运行的示例,在多线程中, 如果出现耗时IO操作等 可能 会导致 单例模式 失效(一个类多个实例),此时 需要添加 线程锁 来保证唯一性(但是 损失了 速度)

如下 代码 简要 表示 多线程中 的 单例模式 实现方式

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren
All rights reserved
create time '2020/10/29 19:52'

Usage:
使用 装饰器 方法实现 多线程下 安全的单例模式
"""
import threading
import time


def singleton(cls):
    """
    单例模式 装饰器
    :param cls:
    :return:
    """

    # 线程锁
    _instance_lock = threading.Lock()

    def _singleton(*args, **kwargs):
        """
        通过 cls._instance 判断是否之前实例化过
        :param args:
        :param kwargs:
        :return:
        """
        # 2次检查 实例是否存在
        if not cls._instance:
            with _instance_lock:
                if not cls._instance:
                    obj = cls(*args, **kwargs)
                    cls._instance = obj
                    return obj
        obj = cls._instance
        return obj

    return _singleton


@singleton
class Foo:
    _instance = None

    def __init__(self, name):
        """

        :param name:
        """
        # 表示 耗时操作
        time.sleep(1)
        self.name = name


def task(arg):
    obj = Foo(arg)
    print(obj.name, id(obj))


for i in range(10):
    t = threading.Thread(target=task, args=[i, ])
    t.start()

 

拓展部分:

根据 单例模式的特点,一个 类只能有一个示例,后续的创建 都是调用的同一个内存地址的 对象,相当于 是 直接 读取的缓存数据,那么 便 可以据此 对 单例模式 进行 简单修改 为  不同参数 存不同缓存 对象,相同参数 则 直接返回对应 对象的  缓存功能(类似redis缓存);

# -*- coding: utf-8 -*-
"""
(C) Guangcai Ren <rgc@bvrft.com>
All rights reserved
create time '2020/10/29 19:52'

Usage:
使用 装饰器 方法实现 单例模式 的拓展功能 -- 参数校验 级别 的 缓存
"""
import random
import time


def singleton_cache(cls):
    """
    单例模式 根据 被装饰 类 初始化 的 参数 进行判断,如果参数已存在,则直接返回对应示例,否则 新建实例,并存入 缓存dict 则装饰器
    :param cls:
    :return:
    """

    def _singleton(*args, **kwargs):
        """
        通过 cls._cache 判断是否之前实例化过
        :param args:
        :param kwargs:
        :return:
        """
        key = str(*args)
        # 通过 key 判断 之前是否生成过 对应示例
        if key not in cls._cache:
            obj = cls(*args, **kwargs)
            # 存入缓存
            cls._cache[key] = obj
        else:
            # 取缓存
            obj = cls._cache[key]
        return obj

    return _singleton


@singleton_cache
class Foo:
    _cache = {}

    def __init__(self, name):
        """

        :param name:
        """
        # 表示 耗时操作
        self.name = name
        self.val = self._action()

    def _action(self):
        """
        表示 耗时操作
        :return:
        """
        time.sleep(1)
        return random.randint(1, 100)

    def __str__(self):
        return f'取出缓存数据,key:{str(self.name)},value:{str(self.val)}'


if __name__ == '__main__':
    start_time = time.time()
    # 新建,存入缓存
    foo1 = Foo('foo1')
    # 新建,存入缓存
    foo2 = Foo('foo2')
    # 读取缓存
    foo3 = Foo('foo1')
    print(id(foo1), foo1)
    print(id(foo2), foo2)
    print(id(foo3), foo3)
    print(f'耗时:{time.time() - start_time}')  # 耗时2s,因为 foo3 是读取的缓存数据


 

总结: 

单例模式非常容易理解,比其他几个创建型 模式 要简单很多, 但是其实现方式 却有很多种,而且 涉及到python高级语法及 多线程安全问题,所以 重点应该放在 实现方式上;

可以根据 单例模式的 特点 进行 灵活运用,从而 实现 参数不同,获取 对象 不同 的 缓存功能;

可以 根据 单例模式的特点 限制 生成 实例的 数量;

 

相关链接:

https://zhuanlan.zhihu.com/p/98440398

https://www.cnblogs.com/huchong/p/8244279.html

http://c.biancheng.net/view/1338.html

https://refactoringguru.cn/design-patterns/singleton

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值