本系列文章是希望将软件项目中最常见的设计模式用通俗易懂的语言来讲解清楚,并通过Python来实现,每个设计模式都是围绕如下三个问题:

  1. 为什么?即为什么要使用这个设计模式,在使用这个模式之前存在什么样的问题?
  2. 是什么?通过Python语言来去实现这个设计模式,用于解决为什么中提到的问题。
  3. 怎么用?理解了为什么我们也就基本了解了什么情况下使用这个模式,不过在这里还是会细化使用场景,阐述模式的局限和优缺点。

这一篇我们先来看看单例模式。单例模式是设计模式中逻辑最简单,最容易理解的一个模式,简单到只需要一句话就可以理解,即“保证只有一个对象实例的模式”。问题的关键在于实现起来并没有想象的那么简单。不过我们还是先来讨论下为什么需要这个模式吧。

为什么

我们首先来看看单例模式的使用场景,然后再来分析为什么需要单例模式。

  • Python的logger就是一个单例模式,用以日志记录
  • Windows的资源管理器是一个单例模式
  • 线程池,数据库连接池等资源池一般也用单例模式
  • 网站计数器

从这些使用场景我们可以总结下什么情况下需要单例模式:

  1. 当每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次;
  2. 当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题。

当然所有使用单例模式的前提是我们的确用一个实例就可以搞定要解决的问题,而不需要多个实例,如果每个实例都需要维护自己的状态,这种情况下单例模式肯定是不适用的。
接下来看看如何使用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>
5
5

不过这个例子中有一个问题我们没有解决,那就是多线程的问题,当有多个线程同时去初始化对象时,就很可能同时判断__instance is None,从而进入初始化instance的代码中。所以为了解决这个问题,我们必须通过同步锁来解决这个问题。以下例子来自xiaorui

import threading
try:
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

这里简单解释下:

  1. 在定义class Highlander的时候已经执行完所有singleton装饰器中的代码,得到了一个instance,所以这之后所有对Highlander的调用实际上是在调用instance的_call_ 方法。
  2. 我们通过lambda函数定义了_call_方法让它始终返回instance,因此Highlander()和Highlander都返回instance
  3. 同时由于在类定义代码执行时就已经创建了instance,所以后续不论是多线程还是单线程,在调用Highlander时都是在调用instance的_call_方法,也就无需同步了。
    最后我想说的是这种方法简直碉堡了~~~
    附上我用于多线程的测试代码
import threading

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:

 

作者:geekpy
链接:https://www.jianshu.com/p/ec6589e02e2f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

设计模式(Python)-单例模式的更多相关文章

  1. python设计模式之单例模式(一)

    前言 单例模式是创建模式中比较常见和常用的模式,在程序执行的整个生命周期只存在一个实例对象. 系列文章 python设计模式之单例模式(一) python设计模式之常用创建模式总结(二) python ...

  2. python设计模式之单例模式(二)

    上次我们简单了解了一下什么是单例模式,今天我们继续探究.上次的内容点这 python设计模式之单例模式(一) 上次们讨论的是GoF的单例设计模式,该模式是指:一个类有且只有一个对象.通常我们需要的是让 ...

  3. python设计模式之单例模式(转)

    设计模式之单例模式 单例设计模式是怎么来的?在面向对象的程序设计中,当业务并发量非常大时,那么就会出现重复创建相同的对象,每创建一个对象就会开辟一块内存空间,而这些对象其实是一模一样的,那么有没有办法 ...

  4. python_way,day8 面向对象【多态、成员--字段 方法 属性、成员修饰符、特殊成员、异常处理、设计模式之单例模式、模块:isinstance、issubclass】

    python_way day8 一.面向对象三大特性: 多态 二.面向对象中的成员 字段.方法属性 三.成员修饰符 四.特殊成员 __init__.__doc__.__call__.__setitem ...

  5. 文成小盆友python-num8 面向对象中的成员,成员修饰符,特殊成员,异常处理,设计模式之单例模式

    本节主要内容: 1.面向对象中的成员 2.成员修饰符 3.特殊成员 4.异常处理 5.设计模式之单例模式 一.面向对象中的成员(类的成员) 类的成员总共可以分为3大类,每类中有不同的分支. 1.总述, ...

  6. 大话设计模式--Python

    作者:五岳 出处:http://www.cnblogs.com/wuyuegb2312 上一周把<大话设计模式>看完了,对面向对象技术有了新的理解,对于一个在C下写代码比较多.偶尔会用到一 ...

  7. python单例模式的实现与优化

    python单例模式的实现与优化 阅读目录(Content) 单例模式 实现单例模式的几种方式 1.使用模块 2.使用装饰器 3.使用类 4.基于__new__方法实现(推荐使用,方便) 5.基于me ...

  8. 设计模式之单例模式(Singleton)

    设计模式之单例模式(Singleton) 设计模式是前辈的一些经验总结之后的精髓,学习设计模式可以针对不同的问题给出更加优雅的解答 单例模式可分为俩种:懒汉模式和饿汉模式.俩种模式分别有不同的优势和缺 ...

  9. GJM : C#设计模式(1)——单例模式

    感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...

随机推荐

  1. 使用U盘安装linux系统

    1. 下载并安装:UltraISO.流氓软件太多,最后是在百度软件中心下的,推荐. 2. 插入U盘.打开UltraISO,然后:文件 -- 打开 -- 选择你的ISO文件,打开. 3. UltraIS ...

  2. JavaScript基础知识笔记

    做前端几年了,一直疏于整理归纳,所以这两天把基础看了一遍,加上使用经验,整理了基础知识中关键技术,旨在系统性的学习和备忘.如果发现错误,请留言提示,谢谢! 重要说明:本文只列举基础知识点,中级和高级内 ...

  3. PHP搞笑注释代码-佛祖配美女

    //////////////////////////////////////////////////////////////////// // _ooOoo_ // // o8888888o // / ...

  4. 20145314郑凯杰《网络对抗技术》可选实验 shellcode注入与Return-to-libc攻击实验

    20145314郑凯杰<网络对抗技术>可选实验 shellcode注入与Return-to-libc攻击实验 1.0 实践内容 Return-to-libc攻击是一种特殊的缓冲区溢出攻击, ...

  5. kali_install_complete_no_sound

    参考:http://tieba.baidu.com/p/4343219808 用pulseaudio --start会看到一些信息,提示类似root用户之类的 我是用下面这个方法搞定的 systemc ...

  6. ajax post data 获取不到数据,注意 content-type的设置 、post/get

    ajax post  data  获取不到数据,注意 content-type的设置 .post/get 关于 jQuery data 传递数据.网上各种获取不到数据,乱码之类的. 好吧今天我也遇到了 ...

  7. 日志自定义Tag

    import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentHashMap; /** * Crea ...

  8. js的函数作用域

    1.js作用域 //在函数内部声明的变量,如果不加var,则自动变成window的成员//预处理:扫描代码,看到var或者函数就生效./*function f(){a = 8;//var a = 8; ...

  9. Java Spring-JdbcTemplate

    2017-11-10 22:55:45 Spring 对持久层技术支持 : JDBC : org.springframework.jdbc.core.JdbcTemplate Hibernate3.0 ...

  10. Mybatis四种分页方式

    数组分页 查询出全部数据,然后再list中截取需要的部分. mybatis接口 List<Student> queryStudentsByArray(); xml配置文件 <sele ...