设计模式:单例模式
前言
来啦老铁!
笔者正在学习常见的设计模式,且将设计模式系列学习文章归入 “设计模式学习” 专题,赶快关注专题一起学习吧!
今天我们继续学习:
-
单例模式
备注:笔者的学习资料大部分来源于:菜鸟教程;
学习路径
- 单例模式简介;
- 单例模式代码实现;
- 单例模式优缺点分析;
- 单例模式使用场景介绍;
1. 单例模式简介;
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
-
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
-
主要解决:
一个全局使用的类频繁地创建与销毁。 -
何时使用:
当您想控制实例数目,节省系统资源的时候。 -
如何解决:
判断系统是否已经有这个单例,如果有则返回,如果没有则创建。 -
关键代码:
构造函数是私有的。
2. 单例模式代码实现;
虽然单例模式很 Java 的样子,单其实其他语言一样能用、有用,我们还是从 python 的角度来看下单例模式怎么用代码来实现吧~
据了解,python 中有几种方式可以实现单例模式:
1. 使用模块实现单例;
2. 使用函数装饰器实现单例;
3. 使用类装饰器实现单例;
4. 使用 new 关键字实现单例;
5. 使用 metaclass 实现单例;
1). 使用模块实现单例;
python的模块就是天然的单例模式,因为模块在第一次导入的时候,会生成.pyc文件,当第二次导入的时候,就会直接加载.pyc文件,而不是再次执行模块代码.如果我们把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
作者:莫辜负自己的一世韶光
链接:https://www.jianshu.com/p/6a1690f0dd00
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 创建一个类,例如 Database 类;
import time
class Database:
def __init__(self):
time.sleep(1)
pass
def get_mysql(self):
print("get mysql...")
database = Database()
- 使用 Database 类演示单例模式;
from database import database
def test():
database.get_mysql()
print("id:", id(database))
database.get_mysql()
print("id:", id(database))
if __name__ == '__main__':
test()
-
结果:
单例模式演示
从打印出的对象 id 可以发现,该方式多次使用 database 对象时未创建多个 database 对象,符合单例模式;
2). 使用函数装饰器实现单例;
- 创建一个类,如 Car 类,并用函数装饰器完成单例模式;
def singleton(cls):
_instance = {}
def fn():
if cls not in _instance:
# 实例化类,并用字典存储
_instance[cls] = cls()
# 返回字典中存储的类
return _instance[cls]
return fn
@singleton
class Car:
def __init__(self):
pass
def get_bmw(self):
print("get BMW...")
if __name__ == "__main__":
car_1 = Car()
car_1.get_bmw()
print(id(car_1))
car_2 = Car()
car_2.get_bmw()
print(id(car_2))
- 使用函数装饰器实现的单例模式;
结果:
函数装饰器实现的单例模式
成功~
3). 使用类装饰器实现单例;
- 创建一个类,如 Phone 类:
class Singleton:
def __init__(self, cls):
self._cls = cls
self._instance = {}
def __call__(self):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls()
return self._instance[self._cls]
@Singleton
class Phone:
def __init__(self):
pass
def get_huawei(self):
print("Hua Wei...")
if __name__ == "__main__":
phone_1 = Phone()
phone_1.get_huawei()
print(id(phone_1))
phone_2 = Phone()
phone_2.get_huawei()
print(id(phone_2))
-
使用类装饰器实现的单例模式;
结果:
类装饰器实现的单例模式
与函数装饰器方式类似,成功~
4). 使用 new 关键字实现单例;
- 创建一个类,如 School 类:
class School(object):
_instance = None
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = object.__new__(cls)
return cls._instance
def get_qinghua(self):
print("qing hua...")
if __name__ == '__main__':
school_1 = School()
school_1.get_qinghua()
print(id(school_1))
school_2 = School()
school_2.get_qinghua()
print(id(school_2))
- 使用 new 关键字实现的单例模式;
结果:
__new__ 关键字实现的单例模式
成功~
5). 使用 metaclass 实现单例;
- 了解使用 type 创造类的方法;
在继续实现之前,我们需要了解一个 python 知识点,即使用 type 创造类的方法,其例子类似:
def fn(self):
# self 可以为任意其他字符,这里只是一个类似占位符的东西
print("this is a test")
if __name__ == "__main__":
# clazz 可以为其他任意字符
# "fn" 为可被访问的方法名,也可以为其他任意名字,"fn" 与 fn 可以名字不同
clazz = type("clazz", (), {"fn": fn})
test_class = clazz()
test_class.fn()
这样,我们就会用 type 创建类了,笔者也是第一次使用这种方式,只能说 python 还是有很多隐藏的知识点呀~
- 创建一个类,如 Color 类:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Color(metaclass=Singleton):
def __init__(self):
pass
def get_blue(self):
print("get blue color...")
if __name__ == "__main__":
color_1 = Color()
color_1.get_blue()
print(id(color_1))
color_2 = Color()
color_2.get_blue()
print(id(color_2))
-
使用 mataclass 实现单例模式;
结果:
mataclass 实现单例模式
成功~
注意,以上 5 种实现单例模式的方式,除了第 1 种在多线程场景下,能确保只有 1 个对象被创建,其他的均不能确保只有 1 个对象被创建,“单例”得还是不够,因此,如果在多线程场景下,则需要给对象实例化的时候加锁,例如:
import threading
from time import sleep
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
with Color.__lock__:
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Color(metaclass=Singleton):
__lock__ = threading.Lock()
def __init__(self):
sleep(1)
pass
def get_blue(self):
print("get blue color...")
def worker():
color = Color()
print(id(color), "\n")
if __name__ == "__main__":
# color_1 = Color()
# color_1.get_blue()
# print(id(color_1))
#
# color_2 = Color()
# color_2.get_blue()
# print(id(color_2))
task_list = []
for i in range(5):
t = threading.Thread(target=worker)
task_list.append(t)
for t in task_list:
t.start()
for t in task_list:
t.join()
或者使用装饰器做一个锁,这样就能做到“全局”只有一个实例了~
3. 单例模式优缺点分析;
优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
4. 单例模式使用场景介绍;
- 一个班级只有一个班主任。
- Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
其他参考文献:https://zhuanlan.zhihu.com/p/37534850
如果本文对您有帮助,麻烦点赞、关注!
谢谢!