设计模式学习收藏

设计模式:单例模式

2022-02-07  本文已影响0人  狄仁杰666

前言

来啦老铁!

笔者正在学习常见的设计模式,且将设计模式系列学习文章归入 “设计模式学习” 专题,赶快关注专题一起学习吧!

今天我们继续学习:

备注:笔者的学习资料大部分来源于:菜鸟教程

学习路径

  1. 单例模式简介;
  2. 单例模式代码实现;
  3. 单例模式优缺点分析;
  4. 单例模式使用场景介绍;

1. 单例模式简介;

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

2. 单例模式代码实现;

虽然单例模式很 Java 的样子,单其实其他语言一样能用、有用,我们还是从 python 的角度来看下单例模式怎么用代码来实现吧~

据了解,python 中有几种方式可以实现单例模式:
1. 使用模块实现单例;
2. 使用函数装饰器实现单例;
3. 使用类装饰器实现单例;
4. 使用 new 关键字实现单例;
5. 使用 metaclass 实现单例;

1). 使用模块实现单例;

python的模块就是天然的单例模式,因为模块在第一次导入的时候,会生成.pyc文件,当第二次导入的时候,就会直接加载.pyc文件,而不是再次执行模块代码.如果我们把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。

作者:莫辜负自己的一世韶光
链接:https://www.jianshu.com/p/6a1690f0dd00
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

import time


class Database:
    def __init__(self):
        time.sleep(1)
        pass

    def get_mysql(self):
        print("get mysql...")


database = Database()

from database import database


def test():
    database.get_mysql()
    print("id:", id(database))
    database.get_mysql()
    print("id:", id(database))


if __name__ == '__main__':
    test()

从打印出的对象 id 可以发现,该方式多次使用 database 对象时未创建多个 database 对象,符合单例模式;

2). 使用函数装饰器实现单例;
def singleton(cls):
    _instance = {}

    def fn():
        if cls not in _instance:
            # 实例化类,并用字典存储
            _instance[cls] = cls()
        # 返回字典中存储的类
        return _instance[cls]

    return fn


@singleton
class Car:
    def __init__(self):
        pass

    def get_bmw(self):
        print("get BMW...")


if __name__ == "__main__":
    car_1 = Car()
    car_1.get_bmw()
    print(id(car_1))

    car_2 = Car()
    car_2.get_bmw()
    print(id(car_2))

结果:


函数装饰器实现的单例模式

成功~

3). 使用类装饰器实现单例;
class Singleton:
    def __init__(self, cls):
        self._cls = cls
        self._instance = {}

    def __call__(self):
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls()
        return self._instance[self._cls]


@Singleton
class Phone:
    def __init__(self):
        pass

    def get_huawei(self):
        print("Hua Wei...")


if __name__ == "__main__":
    phone_1 = Phone()
    phone_1.get_huawei()
    print(id(phone_1))

    phone_2 = Phone()
    phone_2.get_huawei()
    print(id(phone_2))

与函数装饰器方式类似,成功~

4). 使用 new 关键字实现单例;
class School(object):
    _instance = None

    def __init__(self):
        pass

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
        return cls._instance

    def get_qinghua(self):
        print("qing hua...")


if __name__ == '__main__':
    school_1 = School()
    school_1.get_qinghua()
    print(id(school_1))

    school_2 = School()
    school_2.get_qinghua()
    print(id(school_2))

成功~

5). 使用 metaclass 实现单例;
def fn(self):
    # self 可以为任意其他字符,这里只是一个类似占位符的东西
    print("this is a test")


if __name__ == "__main__":
    # clazz 可以为其他任意字符
    # "fn" 为可被访问的方法名,也可以为其他任意名字,"fn" 与 fn 可以名字不同
    clazz = type("clazz", (), {"fn": fn})
    test_class = clazz()
    test_class.fn()

这样,我们就会用 type 创建类了,笔者也是第一次使用这种方式,只能说 python 还是有很多隐藏的知识点呀~

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class Color(metaclass=Singleton):
    def __init__(self):
        pass

    def get_blue(self):
        print("get blue color...")


if __name__ == "__main__":
    color_1 = Color()
    color_1.get_blue()
    print(id(color_1))

    color_2 = Color()
    color_2.get_blue()
    print(id(color_2))

成功~

注意,以上 5 种实现单例模式的方式,除了第 1 种在多线程场景下,能确保只有 1 个对象被创建,其他的均不能确保只有 1 个对象被创建,“单例”得还是不够,因此,如果在多线程场景下,则需要给对象实例化的时候加锁,例如:

import threading
from time import sleep


class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        with Color.__lock__:
            if cls not in cls._instances:
                cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
            return cls._instances[cls]


class Color(metaclass=Singleton):
    __lock__ = threading.Lock()

    def __init__(self):
        sleep(1)
        pass

    def get_blue(self):
        print("get blue color...")


def worker():
    color = Color()
    print(id(color), "\n")


if __name__ == "__main__":
    # color_1 = Color()
    # color_1.get_blue()
    # print(id(color_1))
    #
    # color_2 = Color()
    # color_2.get_blue()
    # print(id(color_2))

    task_list = []
    for i in range(5):
        t = threading.Thread(target=worker)
        task_list.append(t)

    for t in task_list:
        t.start()

    for t in task_list:
        t.join()

或者使用装饰器做一个锁,这样就能做到“全局”只有一个实例了~

3. 单例模式优缺点分析;

优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

4. 单例模式使用场景介绍;

  1. 一个班级只有一个班主任。
  2. Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  3. 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
  4. 要求生产唯一序列号。
  5. WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  6. 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

其他参考文献:https://zhuanlan.zhihu.com/p/37534850

如果本文对您有帮助,麻烦点赞、关注!

谢谢!

上一篇 下一篇

猜你喜欢

热点阅读