动机
最近在写一个连接池,而连接池管理类不可避免的需要使用单例来保证所有用户在取得连接时取到的一定是同一个管理对象。故将此模式取来研究一番。
模式介绍
保证一个类仅有一个实例,并提供一个访问它的全局访问点 ——《设计模式》
思路
单例模式是全局只有一个访问点,故对于一个类来说,任何实例化后访问到的都应该是同样的对象。而也有另外一种变通思路,即访问的不是同一个对象,但其中数据是一样的。对于第一种思路,可以在类新建的时候检查是否之前已经被创建过。如果已经被创建过,则返回上次创建的对象,否则新建一个对象。也可以定义一个工厂来直接保存一个类的实例,客户端向工厂请求时则返回此实例。对于第二种思路,可以将类中的属性保存在类中,每次新建类的时候将同样的属性赋予类,这样,虽然每次返回的不是同一个对象,但其属性相同。
实现
如上所述,方法一、三基于第一种思路,方法二基于第二种思路。
方法一
在类的首次实例化时即保存一个全局的对象,之后再次实例化时即直接返回此对象
class Singleton(object):
def __new__(cls):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton,cls).__new__(cls)
return cls._instance
s2=Singleton()
s3=Singleton()
s4=Singleton()
print id(s2),id(s3),id(s4)
结果为
>>>
49981368 49981368 49981368
注:使用此方法需要Singleton类为新式类,即继承了object类才可以使用super来找到父类
方法二
在类中保留一个全局的字典,用来存储类中出现的属性。在new时将实例中的__dict__
用此字典代替,使所有对象均使用此全局字典来保存属性。
class Singleton2(object):
_att={}
def __new__(cls):
o = super(Singleton2, cls).__new__(cls)
o.__dict__ = cls._att
return o
s2=Singleton2()
s3=Singleton2()
s2.a = 3
s3.c = 20
print id(s2),id(s3)
print s2.a,s2.c
print s3.a,s3.c
结果为
>>>
48998440 48998496
3 20
3 20
注:此方法有局限。因为如果在类中使用了__slots__
之后,则无法使用__dict__
来管理属性。例如
class Singleton2(object):
__slots__=['a','b']
_att={}
def __new__(cls):
o = super(Singleton2, cls).__new__(cls)
o.__dict__ = cls._att
return o
s2=Singleton2()
s3=Singleton2()
s2.a = 3
s3.c = 20
print id(s2),id(s3)
print s2.a,s2.c
print s3.a,s3.c
结果报错
>>>
Traceback (most recent call last):
File "C:\Users\xxx\Desktop\so.py", line 38, in <module>
s2=Singleton2()
File "C:\Users\xxx\Desktop\so.py", line 17, in __new__
o.__dict__ = cls._att
AttributeError: 'Singleton2' object has no attribute '__dict__'
另:slots无法继承。即带有slots的类的子类中不再有slots限制。如果想继承父类的slots,则需要在子类中也加入slots,此时限制的效果为父类与子类slots内容的并集。
方法三
另外借助工厂模式,定义一个工厂类或方法。在此工厂中保存一个已经创建好的类实例,
class t:
pass
class Factory:
ins = t()
def createSingleton(self):
return self.ins
if __name__ == '__main__':
s1=Factory()
s2=Factory()
os1=s1.createSingleton()
os2=s2.createSingleton()
print id(os1),id(os2)
结果为
>>>
30703064 30703064