设计模式第四谈:单例模式
这篇文章将会介绍23种设计模式的单例模式。
1、什么是单例模式
“四人帮”编写的《设计模式-可复用面相对象软件的基础》一书中给出定义是:保证一个类只有一个实例,并且提供一个访问它的全局访问点
。
对于一些类来讲,只有一个实例是非常重要的,比如:在访问文件时,只有一个写实例。通过使用单例模式,让类自身创建、保存它的唯一的实例。
2、Python实现
由于Python语言的特性,可以有多种方式实现单例模式。接下来的Python实现方式会涉及一些Python的基础知识,对这方面不太了解的,可查看接下来几篇的文章。
2.1 基于.pyc文件实现
.pyc
文件是.py
文件编译后的二进制文件,.pyc
相比于.py
文件,加载速度会有所提升。在执行.py
文件代码时,会在磁盘上(Linux系统会存放在Package下的pycache文件夹)寻找对应的.pyc
文件,如果.pyc
的文件修改时间晚于.py
文件的修改时间,表示.py
文件没有修改过,可直接加载,反之会重新生成.pyc
文件。
基于以上特性,可以在一个.py
文件中定义一个类并实例化一个类,其他的.py
文件中通过import方式导入实例化的类对象,如下:
Car.py
# coding: utf-8
class Car(object):
def driver(self):
pass
car = Car()
singleton_example.py
# coding: utf-8
from Singleton.pyc.Car import car
if __name__ == '__main__':
car.driver()
2.2 基于装饰器实现
装饰器本质上就是一个函数,这个函数接受其装饰的函数作为参数,并将其以一个新的修改后的函数进行替换。以装饰器实现单例模式代码如下:
Car.py
# coding: utf-8
from functools import wraps
def singleton(cls):
"""装饰器函数"""
_cls_instance = []
@wraps(cls)
def _create_instance():
if len(_cls_instance) == 0:
_cls_instance.append(cls())
return _cls_instance[0]
return _create_instance
@singleton
class Car(object):
def drive(self):
pass
singleton_example.py
# coding: utf-8
from Singleton.decorator.Car import Car
if __name__ == '__main__':
car_bmw = Car()
car_benz = Car()
print(car_bmw)
print(car_benz)
2.3 基于静态方法实现
Car.py
# coding: utf-8
class Car(object):
def __init__(self, *args, **kwargs):
pass
@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(Car, "_instance"):
Car._instance = Car(*args, **kwargs)
return Car._instance
def drive(self):
pass
singleton_example.py
# coding: utf-8
from Singleton.InstanceMethod.Car import Car
if __name__ == '__main__':
car_bmw = Car.instance()
car_benz = Car.instance()
print(car_bmw)
print(car_benz)
2.4 基于__new__
实现
Car.py
# coding: utf-8
class Car(object):
def __new__(cls, *args, **kwargs):
if not hasattr(Car, "_instance"):
Car._instance = object.__new__(cls)
return Car._instance
def __init__(self, *args, **kwargs):
pass
def drive(self):
pass
singleton_example.py
# coding: utf-8
from Singleton.NewAndInit.Car import Car
if __name__ == '__main__':
car_bmw = Car()
car_benz = Car()
print(car_bmw)
print(car_benz)
2.5 基于metaclass
实现
Car.py
# coding: utf-8
class CarMetaClass(type):
_car_instance = None
def __call__(cls, *args, **kwargs):
if not cls._car_instance:
cls._car_instance = super().__call__(cls, *args, **kwargs)
return cls._car_instance
class Car(metaclass=CarMetaClass):
def __init__(self, *args, **kwargs):
pass
def drive(self):
pass
singleton_example.py
# coding: utf-8
from Singleton.MetaClass.Car import Car
if __name__ == '__main__':
car_bmw = Car()
car_benz = Car()
print(car_bmw)
print(car_benz)
2.6 单例模式的多线程安全
以上5种实现单例模式的方法中,使用类实现的后三种模式在使用多线程时,会存在创建多个实例的现象,这时需要在创建类实例是添加一把进程锁。
基于静态方法的实现:
Car.py
import threading
class Car(object):
_instance_lock = threading.Lock()
def __init__(self, *args, **kwargs):
pass
@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(Car, "_instance"):
with Car._instance_lock:
if not hasattr(Car, "_instance"):
Car._instance = Car(*args, **kwargs)
return Car._instance
def drive(self):
pass
基于
__new__
的实现:Car.py
# coding: utf-8
import threading
class Car(object):
_instance_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if not hasattr(Car, "_instance"):
while cls._instance_lock:
if not hasattr(Car, "_instance"):
Car._instance = object.__new__(cls)
return Car._instance
def __init__(self, *args, **kwargs):
pass
def drive(self):
pass
基于
metaclass
的实现:Car.py
# coding: utf-8
import threading
class CarMetaClass(type):
_car_instance = None
_instance_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if not cls._car_instance:
with cls._instance_lock:
if not cls._car_instance:
cls._car_instance = super().__call__(cls, *args, **kwargs)
return cls._car_instance
class Car(metaclass=CarMetaClass):
def __init__(self, *args, **kwargs):
pass
def drive(self):
pass
3、单例模式UML类图
基于以上的Python代码来看,单例模式只有一个类参与,其UML类图如下图所示。
4、单例模式优缺点
优点:
单例模式只有一个类实例,这样可以减少内存开销和性能开销,特别是当某个对象需要频繁的创建和销毁时
减少对资源的重复使用
单例模式可以在系统设置全局的访问点,优化和共享资源访问
缺点
单例模式一般没有接口,很难扩展,如果需要扩展,只能修改源代码
在多线程开发中,单例模式需要进程锁,会损耗一部分性能,同时对于测试来说也是不利的
5、单例模式适用场景
在如下情景可使用单例模式:
当一个类只能有一个实例,并且可以从全局访问到它
当要严格控制全局变量个数时,可以使用单例模式