Bootstrap

python中的设计模式:单例模式

设计模式

设计模式的确切数量并没有一个统一的标准,因为不同的资料和文献可能会对设计模式的定义和分类有所不同。然而,最常见的设计模式集合是由Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides这四位作者在他们的著作《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)中提出的23种模式。

这23种模式通常被分为三类:

  1. 创建型模式(Creational Patterns):提供了对象创建的机制,能够增加已有代码的灵活性和可重用性。

    • 单例模式(Singleton)
    • 工厂方法模式(Factory Method)
    • 抽象工厂模式(Abstract Factory)
    • 建造者模式(Builder)
    • 原型模式(Prototype)
  2. 结构型模式(Structural Patterns):关注类和对象的组合,以形成更大的结构。

    • 适配器模式(Adapter)
    • 装饰器模式(Decorator)
    • 代理模式(Proxy)
    • 外观模式(Facade)
    • 桥接模式(Bridge)
    • 组合模式(Composite)
    • 享元模式(Flyweight)
  3. 行为型模式(Behavioral Patterns):涉及对象之间的通信,以及在不同对象之间分配责任和算法。

    • 责任链模式(Chain of Responsibility)
    • 命令模式(Command)
    • 解释器模式(Interpreter)
    • 迭代器模式(Iterator)
    • 中介者模式(Mediator)
    • 备忘录模式(Memento)
    • 观察者模式(Observer)
    • 状态模式(State)
    • 策略模式(Strategy)
    • 模板方法模式(Template Method)
    • 访问者模式(Visitor)

除了这23种模式之外,随着软件开发实践的发展,其他的设计模式也被提出和使用。例如,有一些设计模式专门针对特定领域,如用户界面设计模式、企业应用架构模式等。因此,设计模式的总数可能会随着时间和社区的发展而增加。不过,上述23种模式是最基础和最广泛认可的设计模式集合。

单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。这种模式在需要全局状态或者需要频繁创建和销毁对象时非常有用,因为它可以减少资源消耗并提高性能。

应用场景

  • 当你想要控制一个实例的创建,确保在整个应用程序中只使用一个实例时。
  • 当一个实例需要频繁地创建和销毁,而这些操作开销很大时。
  • 当一个实例需要作为其他实例的工厂时。
  • 当需要一个全局访问点,但希望避免在每次调用时创建新实例时。

特点

  • 唯一性:单例模式确保一个类只有一个实例存在。
  • 全局访问:提供了一个全局访问点,可以方便地获取到这个唯一的实例。
  • 延迟初始化:单例实例可以在真正需要时才进行初始化,这有助于提高程序启动速度和节省资源。

优缺点

  • 优点

    • 资源节省:由于只有一个实例,可以减少资源消耗。
    • 全局访问点:提供一个统一的访问点,方便获取实例。
    • 控制实例个数:确保某个类只有一个实例,便于控制。
  • 缺点

    • 可扩展性差:单例模式把控制对象实例的生成全权交给了类本身,无法进行扩展。
    • 滥用风险:单例模式容易滥用,导致系统难以维护和调试。
    • 线程安全问题:在多线程环境下,需要考虑线程安全问题。

python中的实现方式

1. 使用模块

Python的模块在一个Python解释器进程中只加载一次,因此模块自然就是一个单例。你可以简单地将你的类定义在一个模块中,然后通过导入该模块来访问这个类的唯一实例。

class Singleton:
    pass

_singleton = Singleton()

def get_singleton():
    return _singleton

使用时,只需从模块中获取实例:

# 使用模块导入实现
get_singleton = __import__("64 单例模式(导入的模块)").get_singleton
print(get_singleton())
print(get_singleton())
print(get_singleton())

2. 使用类变量和覆写 __new__ 方法

你可以覆写类的 __new__ 方法来控制实例的创建过程,确保只创建一个实例。

# 使用类变量和覆写 `__new__` 方法
class Singleton1:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super().__new__(cls, *args, **kwargs)
        return cls._instance


a = Singleton1()
b = Singleton1()
print(a)
print(b)
print(a == b)

3. 使用装饰器

创建一个装饰器来封装单例的逻辑,使得你可以简单地通过装饰一个类来使其成为单例。

第一次尝试实现,发现报错

# TypeError: 'Singleton2' object is not callable
# 装饰器必须放回的是个函数!不然Singleton2()返回是个实例,当然不可以再调用:Singleton2()()
def singleton(cls, *args, **kwargs):
    _instance = {}
    if cls not in _instance:
        _instance[cls] = cls(*args, **kwargs)
    return _instance[cls]

@singleton
class Singleton2:
    pass


a = Singleton2()
b = Singleton2()
print(a)
print(b)

print(a == b)

执行发现会报错,原因如下:

  1. _instance字典被定义在了singleton函数内部,这意味着每次调用装饰器时都会重新创建一个新的空字典。因此,即使同一个类多次实例化也无法保证单例模式。
  2. 装饰器应该返回一个新的函数或者类,而不是直接返回实例对象。在当前写法中,当使用@singleton修饰Singleton2类时,并没有正确地返回一个可调用对象(比如工厂函数),而是直接返回了Singleton2类的实例。

修正后:

def singleton(cls):
    instances = {}

    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return get_instance


@singleton
class Singleton2:
    pass


a = Singleton2()
b = Singleton2()
print(a)
print(b)

print(a == b)

修正版本中:

  • 使用了一个名为 instances 的字典来存储类及其对应的单一实例。
  • 定义了一个内部包装函数 get_instance() 来检查给定类是否已经有对应的单一实例存在于字典中;如果不存在,则创建一个新实例并存储起来;如果已存在,则直接返回该实例。
  • 最终返回这个内部包装函数而非直接返回类或者类的新实例。这样确保了每次通过该装饰器获取到的都是同一个单一示例。
*args, **kwargs,这两个参数有什么用?

*args**kwargs 是用来接收任意数量的位置参数和关键字参数的。这两个参数在装饰器模式中非常有用,因为它们允许你创建一个通用装饰器,该装饰器可以适应任何具有不同初始化参数的类。

即使在示例代码中没有显式地传递任何值给 Singleton2 类,保留 *args**kwargs 也是一个好习惯。这样做可以增加代码的灵活性和可重用性。如果将来你需要实现一个需要初始化参数的单例类,那么已经存在的单例装饰器就可以直接使用了。

例如:

def singleton(cls):
    instances = {}
    print("装饰器调用一次")

    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    # def get_instance():
    #     if cls not in instances:
    #         instances[cls] = cls()
    #     return instances[cls]

    return get_instance


@singleton
class Singleton2:
    def __init__(self, data):
        self.data = data


a = Singleton2("a")
b = Singleton2("b")
print(a)
print(a.data)
print(b)
print(b.data)

print(a == b)

如果我们没有在定义 get_instance() 函数时包含 *args**kwargs 参数,那么上面这段代码就会抛出异常,因为我们试图传递一个未被接受的参数给构造函数。

4. 使用元类

通过定义一个元类,你可以在类的创建过程中控制实例的生成。

# 使用元类实现
class Meta(type):
    instance = {}

    def __new__(cls, name, base, dct):
        return super().__new__(cls, name, base, dct)

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


class Singleton3(metaclass=Meta):
    pass


a = Singleton3()
b = Singleton3()
print(a)
print(b)

print(a == b)

5. 使用全局变量

在函数内部创建一个类的实例,并将其存储为全局变量,从而实现单例模式。

class _Singleton:
    pass

_instance = _Singleton()

def get_singleton():
    return _instance

6. 使用线程安全的单例模式

如果你的应用是多线程的,你可能需要确保单例模式在多线程环境下也是安全的。可以使用锁来确保只有一个线程可以创建实例。

#  使用线程安全的单例模式
from threading import Lock


class Singleton4:
    instance = None
    lock = Lock()

    def __new__(cls, *args, **kwargs):
        if not cls.instance:
            with cls.lock:
                if not cls.instance:
                    cls.instance = super().__new__(cls, *args, **kwargs)

        return cls.instance


a = Singleton4()
b = Singleton4()
print(a)
print(b)

print(a == b)
;