一、什么是“单例模式”——一个实例
单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保
某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
关键实现思想:
(1)保证构造函数是私有的(比如java、C++、C#)语言,像python这种没办法构造私有函数的怎么办呢?后面会讲到它灵活的实现。
(2)第一次创建类的对象的时候判断系统是否已经有这个单例,如果有则返回,如果没有则创建。那么后续再次创建该类的实例的时候,因为已经创建过一次了,就不能再创建新的实例了,否则就不是单例啦,直接返回前面返回的实例即可。
二、为什么要实现单例模式呢?
常见的单例模式:
(1)比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。
(2)当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题。
(3)Python的logger就是一个单例模式,用以日志记录;Windows的资源管理器是一个单例模式;线程池,数据库连接池等资源池一般也用单例模式;网站计数器等等,这些都是单例模式。
需要使用单例模式的情景:——
这个很重要哦
当每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次;归纳为以下几条:
1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
优缺点:
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
三、单例模式的python实现
不同的编程语言对于单例模式的实现有所不同,因为每一种语言的语法有所差异,但是所遵循的核心原理以及所要达到的最终目的是一样的,不一样的是,往目的地所走的路不一样而已。python实现单例模式的方法有很多,本文着重讲解三种。
1、首先看一下普通的类——即非单例类
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
s1=Student('张三',23)
s2=Student('张三',23)
print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep=' ')
运行结果如下:
False
False
2215920481224 2215920481112
从上面可以看出,在非单例模式下,两个实例是完全不一样的。
2、使用“装饰器”来实现单例模式
def Singleton(cls): #这是一个函数,目的是要实现一个“装饰器”,而且是对类型的装饰器
'''
cls:表示一个类名,即所要设计的单例类名称,
因为python一切皆对象,故而类名同样可以作为参数传递
'''
instance = {}
def singleton(*args, **kargs):
if cls not in instance:
instance[cls] = cls(*args, **kargs) #如果没有cls这个类,则创建,并且将这个cls所创建的实例,保存在一个字典中
return instance[cls]
return singleton
@Singleton
class Student(object):
def __init__(self, name,age):
self.name=name
self.age=age
s1 = Student('张三',23)
s2 = Student('李四',24)
print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep=' ')
运行结果为:
True
True
2150787003840 2150787003840
从上面可以看出,虽然创建的两个对象s1 s2看起来是不一样的,但是实际上都是一样的,这就是
“单例模式”
3、通过__new__函数去实现
class Student(object):
instance = None
def __new__(cls, name,age):
if not cls.instance:
cls.instance = super(Student, cls).__new__(cls)
return cls.instance
def __init__(self,name,age):
self.name=name
self.age=age
s1 = Student('张三',23)
s2 = Student('李四',24)
print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep=' ')
运行结果如下:
True
True
1718008231696 1718008231696
从上面可见,依然是实现了单例模式。
总结:上面这两种方法,都是python实现单例模式最常用的方法,通过比较发现,这两种方法都有一个共同点,那就是都是通过一个内部变量
instance作为桥梁,如果它不是空,就创建一个类的实力绑定到它上面,后续它不为空了,会一直都是这个绑定的对象,这就实现了单例模式。
4、使用一个单独的模块作为单例模式
因为,
Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成
.pyc
文件,当第二次导入时,就会直接加载
.pyc
文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:
在一个模块中定义一个普通的类,如在Singleton_Pattern.py模块中定义如下代码
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
student=Student('张三',23)
这里的student就是一个单例。当我们在另外一个模块中导入student这个对象时,因为它只被导入了一次,所以总是同一个实例。
当然python的单例模式非常的灵活多变,这也是因为Python语言的灵活性所决定的,我们还可以使用元类创建单例模式,甚至还有很多其他的方式都可以创家门单例模式,只要记住单例模式的本质即可。推荐使用前面的两种方法,即
装饰器方法和__new__
方法。因为原理清晰简单,很容易理解。