文章目录
- 一、单例模式介绍
- 1.1 应用场景:
- 二、单例模式的几种创建方式:
- 2.1.经典模式创建:
- 2.2 懒汉式创建
- 2.3 模块级别的单例模式
- 2.4 Monostate单例模式(单态)
- 2.5 单例和元类
- 2.5.1 什么是元类
- 2.5.2 自定义元类
- 2.5.3 基于元类方式的单例创建
- 三、单例模式的缺点
一、单例模式介绍
单例设计模式是应用开发过程中最简单和最著名的一种创建型设计模式。
1.1 应用场景:
- 配置管理:在应用程序中,通常会有一些全局配置,例如数据库连接、日志记录器等。使用单例模式可以确保只有一个配置实例,并且可以在整个应用程序中访问该实例。
- 日志记录:日志记录器通常只需要一个实例。使用单例模式可以确保在整个应用程序中只有一个日志记录器实例,并且可以轻松地访问它。
- 缓存:缓存通常只需要一个实例。使用单例模式可以确保在整个应用程序中只有一个缓存实例,并且可以轻松地访问它。
- 线程池:线程池通常只需要一个实例。使用单例模式可以确保在整个应用程序中只有一个线程池实例,并且可以轻松地访问它。
二、单例模式的几种创建方式:
2.1.经典模式创建:
实现单例模式的一个简单方法是,使构造函数私有化,并创建一个静态方法来完成对象的初始化。这样,对象将在第一次调用时创建,此后,这个类将返回同一个对象。
在使用 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()的时候,我们正是通过这种方式来实现懒汉式实例化的。
class Singleton:_instance = Nonedef __init__(self):if not Singleton._instance:print("类已初始化,但实例未创建!")else:print("实例已创建", self.getInstance())@classmethoddef getInstance(cls):if not cls._instance:cls._instance = Singleton()return cls._instance
s = Singleton() # 类已初始化,但未创建对象
print("开始创建对象", Singleton.getInstance())
s1 = Singleton() # 此时对象已存在,不再创建新的
2.3 模块级别的单例模式
默认情况下,所有的模块都是单例,这是由 Python的导入行为所决定的
Python通过下列方式来工作。
- 检查一个Python模块是否已经导入。
- 如果已经导入,则返回该模块的对象。如果还没有导入,则导入该模块,并实例化。
- 因此,当模块被导入的时候,它就会被初始化。然而,当同一个模块被再次导入的时候,它不会再次初始化,因为单例模式只能有一个对象,所以,它会返回同一个对象。
2.4 Monostate单例模式(单态)
在Monostate单例模式中,一个类有且只有一个对象,但与传统的单例模式不同的是,它关注的是实例的状态,而不是实例本身。因此,Monostate单例模式适合于需要让多个实例共享相同状态的情况。
Monostate单例模式的应用场景包括日志记录、数据库操作、打印机后台处理程序等,这些程序在运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求。
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)来创建类。
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 = xself.y = y
i = int(4, 5)
对于已经存在的类来说,当需要创建对象时,将调用 Python 的特殊方法__call__,在这段代码中,当我们使用int(4,5)实例化int 类时MyInt 元类的 call 方法将被调用,这意味着现在元类控制着对象的实例化。
2.5.3 基于元类方式的单例创建
前面的思路同样适用于单例设计模式。由于元类对类创建和对象实例化有更多的控制权,所以它可以用于创建单例。(注意:为了控制类的创建和初始化,元类将覆盖 __new__和 init 方法。)
- 日志类的实现:
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)
- 数据库连接池的实现
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 = Nonedef 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) # 可以看出两个结果是一致的
三、单例模式的缺点
虽然单例模式在许多情况下效果很好,但这种模式仍然存在一些缺陷。由于单例具有全局访问权限,因此可能会出现以下问题
● 全局变量可能在某处已经被误改,但是开发人员仍然认为它们没有发生变化,而该变量还在应用程序的其他位置被使用。
● 可能会对同一对象创建多个引用。由于单例只创建一个对象,因此这种情况下会对同一个对象创建多个引用。
● 所有依赖于全局变量的类都会由于一个类的改变而紧密耦合为全局数据,从而可能在无意中影响另一个类。