单例模式
理解单例模式
单例模式提供了这样一个机制,即确保类有且只有一个特定类型的对象,并提供全局访问点。因此,单例模式通常用于下列情形,例如日志记录或数据库操作、打印机后台处理程序,以及其他程序 — 该程序运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求。
例如,我们可能希望使用一个数据库对象对数据库进行操作,以维护数据的一致性;或者希望使用一个日志类的对象,将多项服务的日志信息按照顺序转储到一个特定的日志文件中。
简言之,单例设计模式的意图如下所示:
- 确保类有且只有一个对象被创建。
- 为对象提供一个访问点,以使程序可以全局访问该对象。
- 控制共享资源的并行访问。
Python实现的单例模式
普通青年版
通过重写类的__new__
方法来控制对象的创建。
class Singleton(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(Singleton, cls).__new__(cls)
return cls.instance
s = Singleton()
print(s) # <__main__.Singleton object at 0x0000023C753BB198>
s1 = Singleton()
print(s1) # <__main__.Singleton object at 0x0000023C753BB198>
模块级别的单例模式
默认情况下,所有的模块都是单例,这是由 Python 的导入行为所决定的。
Python 通过下列方式来工作:
- 检查一个 Python 模块是否已经导入。
- 如果已经导入,则返回该模块的对象。如果还没有导入,则导入该模块,并实例化。
- 因此,当模块被导入的时候,它就会被初始化。然而,当同一个模块被再次导入的时候,它不会再次初始化,因为单例模式只能有一个对象,所以,它会返回同一个对象。
单态模式
GoF(the Gang of Four,GoF)的单例设计模式是指,一个类有且只有一个对象。然而,根据 Alex Martelli 的说法,通常程序员需要的是让实例共享相同的状态。他建议开发人员应该关注状态和行为,而不是同一性。由于该概念基于所有对象共享相同状态,因此它也被称为 Monostate(单态)模式。
class Borg:
__shared_state = {}
def __init__(self):
self.__dict__ = self.__shared_state
pass
b = Borg()
b1 = Borg()
b.name = 'mxt'
print(b, b.__dict__) # <__main__.Borg object at 0x00000203F1061588> {'name': 'mxt'}
print(b1, b1.__dict__) # <__main__.Borg object at 0x00000203F10615F8> {'name': 'mxt'}
b1.age = 18
print(b.__dict__) # {'name': 'mxt', 'age': 18}
print(b1.__dict__) # {'name': 'mxt', 'age': 18}
上面的代码中,我们将类变量__shared_state
赋给了特殊变量__dict__
,用来存储一个类所有对象的状态。
两个实例“b”和“b1”是两个不同的对象,这一点与单例模式大为不同,后者只能生成一个对象。然而,对象的状态,
即b.__dict__
和 b.__dict__
却是相同的。
也可以通过__new__
实现单态
class Borg(object):
_shared_state = {}
def __new__(cls, *args, **kwargs):
obj = super(Borg, cls).__new__(cls, *args, **kwargs)
obj.__dict__ = cls._shared_state
return obj
通过元类实现单例
# 类的两种创建方法
class A(object):
name = 'mxt'
B = type('B', (object,), {'name': 'mxt'})
Python中可以通过A=type(name,bases,dict)
创建一个类,这种方式有三个参数:
- name:这是类的名称。
- base:这是基类。
- dict:这是属性变量。
现在,如果一个类有一个预定义的元类,那么 Python 就会通过A=MetaCls(name,bases,dict)来创建类。
元类实现的单例:
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
return cls._instances[cls]
# 只要指定SingletonMeta元类,创建的这个类就是单例的
class Logger(metaclass=SingletonMeta):
pass
logger1 = Logger()
logger2 = Logger()
print(logger1) # <__main__.Logger object at 0x000001E2CE1C1630>
print(logger2) # <__main__.Logger object at 0x000001E2CE1C1630>
单例模式的缺点
虽然单例模式在许多情况下效果很好,但这种模式仍然存在一些缺陷。由于单例具有全局访问权限,因此可能会出现以下问题:
- 全局变量可能在某处已经被误改,但是开发人员仍然认为它们没有发生变化,而该变量还在应用程序的其他位置被使用。
- 可能会对同一对象创建多个引用。由于单例只创建一个对象,因此这种情况下会对同一个对象创建多个引用。
- 所有依赖于全局变量的类都会由于一个类的改变而紧密耦合为全局数据,从而可能在无意中影响另一个类。