本系列文章是希望将软件项目中最常见的设计模式用通俗易懂的语言来讲解清楚,并通过Python来实现,每个设计模式都是围绕如下三个问题:
这一篇我们先来看看单例模式。单例模式是设计模式中逻辑最简单,最容易理解的一个模式,简单到只需要一句话就可以理解,即“保证只有一个对象实例的模式”。问题的关键在于实现起来并没有想象的那么简单。不过我们还是先来讨论下为什么需要这个模式吧。
为什么
我们首先来看看单例模式的使用场景,然后再来分析为什么需要单例模式。
- Python的logger就是一个单例模式,用以日志记录
- Windows的资源管理器是一个单例模式
- 线程池,数据库连接池等资源池一般也用单例模式
- 网站计数器
从这些使用场景我们可以总结下什么情况下需要单例模式:
当然所有使用单例模式的前提是我们的确用一个实例就可以搞定要解决的问题,而不需要多个实例,如果每个实例都需要维护自己的状态,这种情况下单例模式肯定是不适用的。
接下来看看如何使用Python来实现一个单例模式。
是什么
最开始的想法很简单,实现如下:
class Singleton(object):__instance = None
def __new__(cls, *args, **kwargs): # 这里不能使用__init__,因为__init__是在instance已经生成以后才去调用的
if cls.__instance is None:
cls.__instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls.__instance
s1 = Singleton()
s2 = Singleton()
print s1
print s2
打印结果如下:
<__main__.Singleton object at 0x7f3580dbe110><__main__.Singleton object at 0x7f3580dbe110>
可以看出两次创建对象,结果返回的是同一个对象实例,我们再让我们的例子更接近真实的使用场景来看看
class Singleton(object):__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super(
Singleton, cls).__new__(cls, *args, **kwargs)
return cls.__instance
def __init__(self, status_number):
self.status_number = status_number
s1 = Singleton(2)
s2 = Singleton(5)
print s1
print s2
print s1.status_number
print s2.status_number
这里我们使用了_init_方法,下面是打印结果,可以看出确实是只有一个实例,共享了实例的变量
<__main__.Singleton object at 0x7f5116865490><__main__.Singleton object at 0x7f5116865490>
不过这个例子中有一个问题我们没有解决,那就是多线程的问题,当有多个线程同时去初始化对象时,就很可能同时判断__instance is None,从而进入初始化instance的代码中。所以为了解决这个问题,我们必须通过同步锁来解决这个问题。以下例子
import threadingtry:
from synchronize import make_synchronized
except ImportError:
def make_synchronized(func):
import threading
func.__lock__ = threading.Lock()
def synced_func(*args, **kws):
with func.__lock__:
return func(*args, **kws)
return synced_func
class Singleton(object):
instance = None
@make_synchronized
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = object.__new__(cls, *args, **kwargs)
return cls.instance
def __init__(self):
self.blog = "xiaorui.cc"
def go(self):
pass
def worker():
e = Singleton()
print id(e)
e.go()
def test():
e1 = Singleton()
e2 = Singleton()
e1.blog = 123
print e1.blog
print e2.blog
print id(e1)
print id(e2)
if __name__ == "__main__":
test()
task = []
for one in range(30):
t = threading.Thread(target=worker)
task.append(t)
for one in task:
one.start()
for one in task:
one.join()
至此我们的单例模式实现代码已经接近完美了,不过我们是否可以更简单地使用单例模式呢?答案是有的,接下来就看看如何更简单地使用单例模式。
怎么用
在Python的官方网站给了两个例子是用装饰符来修饰类,从而使得类变成了单例模式,使得我们可以通过更加简单的方式去实现单例模式
例子:(这里只给出一个例子,因为更简单,另外一个大家可以看官网Singleton
def singleton(cls):instance = cls()
instance.__call__ = lambda: instance
return instance
#
# Sample use
#
@singleton
class Highlander:
x = 100
# Of course you can have any attributes or methods you like.
Highlander() is Highlander() is Highlander #=> True
id(Highlander()) == id(Highlander) #=> True
Highlander().x == Highlander.x == 100 #=> True
Highlander.x = 50
Highlander().x == Highlander.x == 50 #=> True
这里简单解释下:
最后我想说的是这种方法简直碉堡了~~~
附上我用于多线程的测试代码
def singleton(cls):
instance = cls()
instance.__call__ = lambda: instance
return instance
@singleton
class Highlander:
x = 100
# Of course you can have any attributes or methods you like.
def worker():
hl = Highlander()
hl.x += 1
print hl
print hl.x
def main():
threads = []
for _ in xrange(50):
t = threading.Thread(target=worker)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
if __name__ == '__main__':
main()
这里的代码有一点小问题,就是在打印的时候有可能x属性已经被别的线程+1了,所以有可能导致同一个数打印多次,而有的数没有打印,但是不影响最终x属性的结果,所以当所有线程结束之后,属性x最终的值是可以保证正确的。
Reference:
- Python官网Singleton装饰器
- Xiaorui Blog
- static未央