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

server/2025/1/15 18:12:00/

设计模式

设计模式的确切数量并没有一个统一的标准,因为不同的资料和文献可能会对设计模式的定义和分类有所不同。然而,最常见的设计模式集合是由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_78">python中的实现方式

1. 使用模块

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

python">class Singleton:pass_singleton = Singleton()def get_singleton():return _singleton

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

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

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

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

python"># 使用类变量和覆写 `__new__` 方法
class Singleton1:_instance = Nonedef __new__(cls, *args, **kwargs):if not cls._instance:cls._instance = super().__new__(cls, *args, **kwargs)return cls._instancea = Singleton1()
b = Singleton1()
print(a)
print(b)
print(a == b)

3. 使用装饰器

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

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

python"># 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:passa = Singleton2()
b = Singleton2()
print(a)
print(b)print(a == b)

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

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

修正后:

python">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:passa = Singleton2()
b = Singleton2()
print(a)
print(b)print(a == b)

修正版本中:

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

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

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

例如:

python">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 = dataa = Singleton2("a")
b = Singleton2("b")
print(a)
print(a.data)
print(b)
print(b.data)print(a == b)

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

4. 使用元类

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

python"># 使用元类实现
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):passa = Singleton3()
b = Singleton3()
print(a)
print(b)print(a == b)

5. 使用全局变量

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

python">class _Singleton:pass_instance = _Singleton()def get_singleton():return _instance

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

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

python">#  使用线程安全的单例模式
from threading import Lockclass Singleton4:instance = Nonelock = 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.instancea = Singleton4()
b = Singleton4()
print(a)
print(b)print(a == b)

http://www.ppmy.cn/server/2398.html

相关文章

js判断文件后缀是图片

function isImage(filename) { // 获取文件的扩展名 const extension filename.split(‘.’).pop().toLowerCase(); // 常见的图片文件扩展名 const imageExtensions [‘jpg’, ‘jpeg’, ‘png’, ‘gif’, ‘bmp’, ‘webp’, ‘svg’]; // 检查文件扩展名是否在常见图片…

iOS知识点 --- UITableView优化

iOS 中的 UITableView 是一个非常常见的用于展示列表数据的组件,由于其在滚动时需要实时加载和更新大量单元格,因此对性能要求较高。以下是一些针对 UITableView 的性能优化策略: 合理利用重用机制: 设置正确的 reuseIdentifier 并…

C语言C/S架构PACS影像归档和通信系统源码 医院PACS系统源码

C语言C/S架构PACS影像归档和通信系统源码 医院PACS系统源码 医院影像科PACS系统,意为影像归档和通信系统。它是应用在医院影像科室的系统,主要的任务是把日常产生的各种医学影像(包括核磁、CT、超声、各种X光机、各种红外仪、显微…

使用Python进行自动化测试【第163篇—自动化测试】

👽发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 如何使用Python进行自动化测试:测试框架的选择与应用 自动化测试是软件开发过程…

【opencv】示例-videocapture_realsense.cpp 捕获英特尔感知摄像头的数据提取深度图、彩色图和红外图...

#include "opencv2/videoio.hpp" // 包含OpenCV视频输入/输出头文件 #include "opencv2/highgui.hpp" // 包含OpenCV高级用户界面头文件 #include "opencv2/imgproc.hpp" // 包含OpenCV图像处理头文件using namespace cv; // 使用opencv命名空…

sklearn的LabelEncoder 遇到新值的解决办法

问题:sklearn的LabelEncoder函数遇到新值报错 sklearn的LabelEncoder函数,在fit结束后,对dataframe数据进行transform的时候,如果遇到了没在fit时编码规则里的新值,会出现代码报错,不同于spark的LabelEnco…

TCP断开连接为什么需要4次挥手?

一、断开连接过程 由于TCP连接是全双工的,因此每个方向都必须单独关闭。客户端在数据发送完毕后发送一个结束数据段FIN,且服务端也返回确认数据段ACK,此时结束了客户端到服务端的连接;然后客户端接收到服务端发送的FIN&#xff0c…

如何快速打开Github

为什么我们打开Github速度很慢?很卡,甚至于访问不了,原因是中间有个域名通过DNS解析的过程,将域名解析为对应的ip地址,主要时间都是花在了DNS解析上了。 我们在浏览器输入 GitHub 的网址时,会向 DNS 服务器…