(三)python单例模式

文章目录

一、单例模式介绍

单例设计模式是应用开发过程中最简单和最著名的一种创建型设计模式。

1.1 应用场景:
  • 配置管理:在应用程序中,通常会有一些全局配置,例如数据库连接、日志记录器等。使用单例模式可以确保只有一个配置实例,并且可以在整个应用程序中访问该实例。
  • 日志记录:日志记录器通常只需要一个实例。使用单例模式可以确保在整个应用程序中只有一个日志记录器实例,并且可以轻松地访问它。
  • 缓存:缓存通常只需要一个实例。使用单例模式可以确保在整个应用程序中只有一个缓存实例,并且可以轻松地访问它。
  • 线程池:线程池通常只需要一个实例。使用单例模式可以确保在整个应用程序中只有一个线程池实例,并且可以轻松地访问它。

二、单例模式的几种创建方式:

2.1.经典模式创建:

实现单例模式的一个简单方法是,使构造函数私有化,并创建一个静态方法来完成对象的初始化。这样,对象将在第一次调用时创建,此后,这个类将返回同一个对象。

在使用 Python 的时候,我们的实现方式要有所变通,因为它无法创建私有的构造函数。下面,我们一起看看如何利用Python语言来实现单例模式。

python 复制代码
class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, 'instance'):
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance
s = Singleton()
print("1-对象创建", s)
s2 = Singleton()
print("2-对象创建", s2)

在上面的代码中,我们通过覆盖__new__方法(python用来实例化对象的特殊方法)来控制对象的创建。对象s就是由__new__方法创建的。

方法hasattr用于查看对象是否具有属性instance,该属性的作用是检查该类是否已经生成了一个对象。

2.2 懒汉式创建

单例模式的用例之一就是懒汉式实例化。例如,在导入模块的时候,我们可能会无意中创建一个对象,但当时根本用不到它。懒汉式实例化能够确保在实际需要时才创建对象。所以,懒汉式实例化是一种节约资源并仅在需要时才创建它们的方式。

在下面的代码示例中,执行 s= singleton()的时候,它会调用 init 方法但没有新的对象被创建。然而,实际的对象创建发生在调用 singleton.getInstance()的时候,我们正是通过这种方式来实现懒汉式实例化的。

python 复制代码
class Singleton:
    _instance = None
    def __init__(self):
        if not Singleton._instance:
            print("类已初始化,但实例未创建!")
        else:
            print("实例已创建", self.getInstance())
    @classmethod
    def getInstance(cls):
        if not cls._instance:
            cls._instance = Singleton()
        return cls._instance
s = Singleton()  # 类已初始化,但未创建对象
print("开始创建对象", Singleton.getInstance())
s1 = Singleton()  # 此时对象已存在,不再创建新的
2.3 模块级别的单例模式

默认情况下,所有的模块都是单例,这是由 Python的导入行为所决定的

Python通过下列方式来工作。

  1. 检查一个Python模块是否已经导入。
  2. 如果已经导入,则返回该模块的对象。如果还没有导入,则导入该模块,并实例化。
  3. 因此,当模块被导入的时候,它就会被初始化。然而,当同一个模块被再次导入的时候,它不会再次初始化,因为单例模式只能有一个对象,所以,它会返回同一个对象。
2.4 Monostate单例模式(单态)

在Monostate单例模式中,一个类有且只有一个对象,但与传统的单例模式不同的是,它关注的是实例的状态,而不是实例本身。因此,Monostate单例模式适合于需要让多个实例共享相同状态的情况。

Monostate单例模式的应用场景包括日志记录、数据库操作、打印机后台处理程序等,这些程序在运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求。

python 复制代码
class Borg:
    __shared_state = {"x": "1"}
    def __init__(self):
        self.y = 2
        #  __dict__ python内置,用来存储一个类所有对象的状态
        self.__dict__ = self.__shared_state
b = Borg()
b1 = Borg()
b.y = 4  # y的属性被所有对象共享
print(b)
print(b1)  # 地址是不同的,
print(b.__dict__)
print(b1.__dict__)  # 两个对象的状态是一致的!
2.5 单例和元类
2.5.1 什么是元类

元类是一个类的类,这意味着该类是它的元类的实例。使用元类,程序员有机会从预定义的 Python 类创建自己类型的类。

例如,如果你有一个对象Myclass,你可以创建一个元类MyKls,它按照你需要的方式重新定义Myclass 的行为。

在Python中,一切皆对象。如果我们说a=5,则type(a)返回<type 'int'>,这意味着a是int 类型。但是,type(int)返回<type 'type'>,这表明存在一个元类,因为int是type类型的类。

类的定义由它的元类决定,所以当我们用类A创建一个类时,Python 通过A=type(name,bases,dict)创建它。其中,name-类的名称; bases-基类; dict-属性变量;

2.5.2 自定义元类

现在,如果一个类有一个预定义的元类(名为 MyInt),那么 Python 就会通过A=MyInt(name,bases,dict)来创建类。

python 复制代码
class MyInt(type):
    def __call__(cls, *args, **kwds):
        print("***** Here's My int *****args")
        print("Now do whatever you want with these objects...")
        return type.__call__(cls, *args, **kwds)
class int(metaclass=MyInt):
    def __init__(self, x, y):
        self.x = x
        self.y = y
i = int(4, 5)

对于已经存在的类来说,当需要创建对象时,将调用 Python 的特殊方法__call__,在这段代码中,当我们使用int(4,5)实例化int 类时MyInt 元类的 call 方法将被调用,这意味着现在元类控制着对象的实例化。

2.5.3 基于元类方式的单例创建

前面的思路同样适用于单例设计模式。由于元类对类创建和对象实例化有更多的控制权,所以它可以用于创建单例。(注意:为了控制类的创建和初始化,元类将覆盖 __new__和 init 方法。)

  • 日志类的实现:
python 复制代码
class MetaSingleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
class Logger(metaclass=MetaSingleton):
    pass
logger1 = Logger()
logger2 = Logger()
print(logger1, logger2)
  • 数据库连接池的实现
python 复制代码
import sqlite3
class MetaSingleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
class Database(metaclass=MetaSingleton):
    connection = None
    def connect(self):
        if self.connection is None:
            self.connection = sqlite3.connect("db.sqlite3")
            self.cursorobj = self.connection.cursor()
        return self.cursorobj
db1 = Database().connect()
db2 = Database().connect()
print("Database Objects DB1", db1)
print("Database Objects DB2", db2)  #  可以看出两个结果是一致的

三、单例模式的缺点

虽然单例模式在许多情况下效果很好,但这种模式仍然存在一些缺陷。由于单例具有全局访问权限,因此可能会出现以下问题

● 全局变量可能在某处已经被误改,但是开发人员仍然认为它们没有发生变化,而该变量还在应用程序的其他位置被使用。

● 可能会对同一对象创建多个引用。由于单例只创建一个对象,因此这种情况下会对同一个对象创建多个引用。

● 所有依赖于全局变量的类都会由于一个类的改变而紧密耦合为全局数据,从而可能在无意中影响另一个类。

相关推荐
ZSYP-S12 分钟前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
yuanbenshidiaos15 分钟前
c++------------------函数
开发语言·c++
程序员_三木27 分钟前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
是小崔啊37 分钟前
开源轮子 - EasyExcel01(核心api)
java·开发语言·开源·excel·阿里巴巴
tianmu_sama43 分钟前
[Effective C++]条款38-39 复合和private继承
开发语言·c++
黄公子学安全1 小时前
Java的基础概念(一)
java·开发语言·python
liwulin05061 小时前
【JAVA】Tesseract-OCR截图屏幕指定区域识别0.4.2
java·开发语言·ocr
jackiendsc1 小时前
Java的垃圾回收机制介绍、工作原理、算法及分析调优
java·开发语言·算法
Oneforlove_twoforjob1 小时前
【Java基础面试题027】Java的StringBuilder是怎么实现的?
java·开发语言
羚羊角uou1 小时前
【C++】优先级队列以及仿函数
开发语言·c++