Python设计模式(第2版)

978-7-115-45880-3
作者: 【印度】Chetan Giridhar(吉里德尔)
译者: 韩波
编辑: 胡俊英
分类: Python

图书目录:

详情

本书分为11章,从认识设计模式开始,本书陆续介绍了众多关于Python设计模式的知识。帮助读者更好地提升软件架构技能,同时理解一系列常见的软件设计方案,深入研究面向对象编程的理念,并将其运用在实际的开发中,帮助读者解决很多实际开发当中的问题,改善开发者的既有产品和代码。

图书摘要

版权信息

书名:Python设计模式(第2版)

ISBN:978-7-115-45880-3

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [印度] Chetan Giridhar

  译    韩 波

  责任编辑 胡俊英

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright ©2016 Packt Publishing. First published in the English language under the title Learning Python Design Patterns, Second Edition.

All rights reserved.

本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。

版权所有,侵权必究。


设计模式是构建大型软件系统最强大的方法之一,优化软件架构和设计已经逐渐成为软件开发和维护过程中的一个重要课题。

本书通过11章内容,全面揭示有关设计模式的内容,并结合Python语言进行示例化的解析。全书囊括单例设计模式、工厂模式、门面模式、代理模式、观察者模式、命令模式、模板方法模式、复合模式、状态设计模式以及反模式等多种设计模式。

本书适合那些关注软件设计原则,并想将优秀的设计模式应用到Python编程当中的读者阅读,也适合普通的软件工程师、架构师参考。


“控制复杂度是计算机编程的本质”。

——Brian Kernighan

“计算机科学中的所有问题都可以通过抽象来解决”。

——David Wheeler

上面引自两位著名计算机科学家的名言,深入阐释了现代软件设计人员所面临的问题,即迫切需要为软件设计提供一个优良、稳定、可重用、灵活的解决方案。

实际上,设计模式能够以最优雅的方式来解决上述问题。设计模式抽象并存在于整洁、精心设计的组件和接口中,是众多软件设计师和架构师在解决类似问题方面长年积累的宝贵经验。在可重用性、灵活性、可扩展性和可维护性方面,这些解决方案都历经了长时间的考验。

目前,除了堪称设计模式奠基之作的Gang of Four(GoF)的作品之外,该领域也涌现出了大量图书。

然而,在这个Web和移动计算的时代,人们越来越倾向于使用诸如Python、Ruby和Clojure之类的高级语言来编写程序,这就需要有更多的图书来将设计模式中那些深奥的语言翻译成人们更熟悉的术语,从而帮助人们使用这些更加新颖、更动态化的编程语言来编写可重用的代码。对于程序员新手来说尤其如此,因为他们通常会在设计与实现的复杂性中迷失方向,从而更需要专家的帮助。

书中不仅沿用了GoF书中的设计模式模板,同时为兼顾完整性,还添加了一些其他模式。但在介绍各种模式之前,首先为年轻和缺乏经验的读者提供了软件设计原则方面的基础知识,它们是这些设计模式产生和发展的思想基础。本书没有将读者一把推进模式世界的迷宫中,而是在打开这扇门之前首先介绍相应的基础知识,然后带领读者沿着这条道路循序渐进地学习。

在本书中,模式的示例代码都是用Python语言编程实现的,这一点是非常有意义的。作为一个为这种精彩的编程语言社团效力了12年多的人,我可以证明,它不仅优美而简洁,而且能够有效地解决各种从常规到最复杂的问题。Python非常适合新手和年轻的程序员,因为它不仅易于学习,而且用它编写代码简直妙趣横生。年轻的程序员将会发现,他们花在Python社区和本书上的时间将是非常有益且卓有成效的。最后,作者Chetan Giridhar在Python方面经验丰富,因为他已经跟它打了7年多的交道了。

本书由他来编写是再合适不过了,因为他多次参与软件的设计和实现,对这些阶段中的复杂性有着切身的体会,并在这个过程学到了很多。同时,他还是Python各种论坛的著名演讲者,并多次在Python大会(如PyCon India)上发表演讲。此外,他曾应邀在美国、亚太地区和新西兰的会议上发表演讲。

我相信本书将是对Packt系列图书的一个很好的补充,并且将为年轻的Python程序员提供一组相应的技能,使他们能够轻而易举通过Python进行模块设计和高效编程。

——Anand B Pillai

CTO-Skoov.com

Python软件基金会董事会成员

班加罗尔Python用户组创始人


Chetan Giridhar是一位技术领导者、开源爱好者和Python开发人员。他曾在LinuxForYouAgile Record等杂志上发表多篇技术和开发实践方面的文章,并在Python Papers杂志上发表过技术论文。他曾在PyCon India、Asia-Pacifc和New ZealandPyCon等会议上发表演讲,并热衷于实时通信、分布式系统和云应用等领域。Chetan已经是Packt出版社的技术评审,并为IPython VisualizationsCore Python等图书撰稿。

我要在此向Packt团队致谢,特别是Merint Thomas Mathew,以及技术评审Maurice HT Ling,感谢他们为本书做出了积极的贡献。特别感谢我的导师Anand B Pillai欣然接受本书的审阅工作,并为本书作序。如果没有我的父母Jyotsana和Jayant Giridhar的祝福,以及我的妻子Deepti和女儿Pihu不断的支持和鼓励,本书是不可能完成的!


Maurice HT Ling自2003年以来一直从事Python编程。在墨尔本大学取得生物信息学博士学位以及分子和细胞生物学的理学士(大学荣誉学位)学位后,他目前担任新加坡南洋理工大学的研究员,以及澳大利亚墨尔本大学的名誉研究员。Maurice是计算和数学生物学的主编,同时也是Python Papers的共同编辑。最近,Maurice共同创立了新加坡首屈一指的合成生物学创业公司AdvanceSyn Pte,并担任公司董事和首席技术官。此外,他还是新加坡Colossus Technologies LLP的主要合伙人。他的研究兴趣主要在生命科学领域(生物生命、人造生命和人工智能),使用计算机科学和统计学作为工具来了解生命及其各个方面。空闲时,Maurice喜欢阅读、品咖啡、写个人日记,或对生命的各个方面进行哲学探讨。你可以在他的个人网站和LinkedIn上访问他的个人主页,地址分别为http://maurice.vodien.com和http://www.linkedin.com/in/mauriceling。


设计模式是构建大型软件系统最强大的方法之一。随着人们对优化软件架构和设计的日益关注,对于软件架构师而言,在架构或设计层面上考虑对象创建、代码结构和对象之间的交互等方面的优化也显得日益重要。因为这样不仅可以让软件的维护成本变低,使得代码可以轻松重用,同时还能使得代码可以适应变化。此外,具有可重用性和独立性的框架是当今软件开发的关键所在。

第1章“设计模式简介”介绍了面向对象编程的基础知识,同时详细讨论了面向对象编程的设计原则。本章简要介绍了设计模式的概念,以便帮读者了解软件开发中设计模式的相关背景和应用。第2章“单例设计模式”讲解了应用程序开发中使用的最简单和最著名的创建型设计模式之一—单例设计模式。同时,还介绍了利用Python创建单例模式的不同方式。此外,本章还介绍了Monostate(或Borg)设计模式,它是单例设计模式的一个变体。第3章“工厂模式”讨论了另一种创建型模式,即工厂模式。同时,本章还借助UML图、现实场景和Python 3.5来帮助读者了解工厂方法模式和抽象工厂模式。

第4章“门面模式”向读者展现了另一种设计模式,即结构型设计模式。我们不仅介绍了门面的概念,并借助门面设计模式介绍了如何将其用于软件设计。同时,还通过实际场景中的Python示例应用程序来介绍其实现过程。

第5章“代理模式”讲解了一种结构型设计模式—代理模式。我们首先介绍了代理的概念,并讨论了相应的设计模式,然后介绍了该模式在软件应用程序开发中如何应用。此外,本章还讲解了代理模式的各种变体—虚拟代理、智能代理、远程代理和保护代理。

第6章“观察者模式”探讨了第三种设计模式—行为型设计模式。在本章中,我们将以实例的形式介绍观察者设计模式。同时,本章还详细展示了如何实现观察者模式的推模型和拉模型以及松耦合原则。在云应用程序和分布式系统方面,这种模式是至关重要的。第7章“命令模式”将为读者介绍命令设计模式。我们不仅介绍了命令设计模式,还借助现实世界场景和相应的Python实现介绍了如何将其应用于软件应用程序开发当中。除此之外,本章还考察了命令模式的两个主要方面,即重做或回滚操作以及异步任务执行的实现。第8章“模板方法模式”讨论了模板设计模式。跟命令模式类似,模板模式也属于行为型模式。在这一章中,不仅讨论了模板方法模式,还通过其实现来介绍了钩子技术。此外,本章还通过好莱坞原则来帮助读者加深对这种模式的理解。

第9章“模型—视图—控制器—复合模式”不仅为读者介绍了该模式本身,还讨论了如何将其应用于软件应用程序开发。MVC已经成为最常用的设计模式之一,其实很多Python框架都是基于这个原理的。读者还可以通过使用Python Tornado(Facebook使用的框架)编写的示例应用程序来了解MVC实现的详细信息。

第10章“状态设计模式”向读者介绍了状态设计模式,就像命令或模板设计模式一样,它们都属于行为型模式。同时,本章还讨论了如何在软件应用程序开发中使用该模式。第11章“反模式”为读者介绍了反模式,即作为架构师或软件工程师,我们不应该采取的那些行为。

对于阅读本书来说,只需安装Python 3.5即可,你可以从https://www.python.org/ downloads/下载并安装该软件。

本书的目标读者是需要关注软件设计原则和Python应用程序开发方面细节的Python开发人员和软件架构师。这要求读者对编程概念有基本了解,同时要具备初级的Python开发经验。此外,对于学生和老师来说,现场学习环境也是颇为有益的。

在本书中,不同类型的信息会采用不同的排版样式,以示区别。下面针对各种排版样式及其含义进行举例说明。

文本、数据库表名、文件夹名、文件名、文件扩展名和路径名、伪URL、用户输入和推特句柄中出现的代码文字,会显示:“对象Car具有诸如fuel levelisSedanspeedsteering wheelcoordinates等属性。”

代码段会显示:

class Person(object):
    def __init__(self, name, age): #constructor
        self.name = name   #data members/ attributes
        self.age = age
    def get_person(self,):   # member function
        return "<Person (%s, %s)>" % (self.name, self.age)
p = Person("John", 32)   # p is an object of type Person
print("Type of Object:", type(p), "Memory Address:", id(p))

新术语及重要词汇使用粗体字表示。对于在屏幕中看到的文字,如菜单或者对话框中的文字,排版形式为“对于Python语言来说,封装(数据和方法的隐藏)的概念不是隐式的,因为它没有提供支持封装所需的关键字,如publicprivateprotected(而C ++或Java语言则提供了相应的关键字)”。

 

提示:

  • 警告或者重要的注释在此显示。

 

 

小技巧:

  • 提示和小技巧在此显示。

我们欢迎读者对本书进行反馈,希望了解你对本书的看法:你喜欢哪些方面或不喜欢哪些方面。在帮助本社推出真正符合读者需要的图书方面,读者的反馈信息至关重要。

如果想为我们提供一般性的反馈,请向feedback@packtpub.com邮箱发送电子邮件,并在邮件的标题中指出相应的书名即可。

如果某些主题是你擅长的领域,并且有意著书或撰稿,请进入www.packtpub.com/ authors,进一步阅读作者指南。

你已经是Packt出版社的尊贵用户,为了让你的订购物超所值,我们将为你提供一些增值服务。

访问http://www.packtpub.com网站并登录账户后,读者便可以下载所有已购Packt出版社图书的示例代码。如果是在其他地方购买的本书,可以访问http://www.packtpub. com/support并注册,通过电子邮件获取相应的代码。

虽然我们会全力确保书中内容的准确性,但错误仍在所难免。如果你在本书中发现了错误(文字错误或代码错误),而且愿意向我们提交这些错误,我们感激不尽。这样不仅可以消除其他读者的疑虑,也有助于改进后续版本。若想提交你发现的错误,请访问http://www.packtpub.com/submit-errata,在“Errata Submission Form”(提交勘误表单)中选择相应图书,输入勘误详情。勘误通过验证之后将上传到Packt网站,或添加到现有的勘误列表中。若想查看某本书的现有勘误信息,请访问http://www. packtpub.com/support,选择相应的书名即可。

对各种媒体而言,互联网上受版权保护的各种材料都长期面临非法复制的问题。Packt出版社非常重视版权保护和版权许可,如果你在网上看到本社图书任何形式的非法复制,请立刻向我们提供网址或网站名称,以便我们及时采取补救措施。

请通过copyright@packtpub.com联系我们,并提供疑似盗版材料的链接信息。

感谢你帮助我们保护作者的权益,从而使我们能够提供更有价值的内容。

如果你对本书有任何疑问,可以通过questions@packtpub.com联系我们,我们将尽力为你解答。


在本章中,我们将详细介绍面向对象编程的基础知识,并深入探讨面向对象的设计原理,以便为本书后面介绍的高级主题打下坚实的基础。此外,本章还将简要介绍设计模式的概念,使你能够了解软件开发中设计模式的背景和应用。在这里,我们也将设计模式分为三大类型:创建型、结构型和行为型模式。因此,本章中主要涵盖以下主题:

在开始学习设计模式之前,我们不妨先来了解一下相关的基础知识,并进一步熟悉Python面向对象的范式。面向对象的世界引入了对象的概念,而这些对象又具有属性(数据成员)和过程(成员函数)。这些函数的作用就是处理属性。

这里,我们以对象Car为例进行说明。对象Car不仅拥有多种属性,如fuel level(油位)、isSedan(是否为轿车)speed(速度)、steering wheel(方向盘)和coordinates(坐标),同时还拥有一些方法,例如accelerate()方法用来提供速度,而takeleft()方法则可以让车左转。自Python的第1版发布之后,它也变成了一种面向对象的语言。正如它声明的那样,在Python中,一切皆对象。每个类的实例或变量都有它自己的内存地址或身份。对象就是类的实例,应用开发就是通过让对象交互来实现目的的过程。为了理解面向对象程序设计的核心概念,我们需要深入理解对象、类和方法。

我们可以通过以下几点来描述对象。

类可以帮助开发人员表示现实世界中的实体。

例如,类Person可以带有属性nameage,同时提供成员函数gotoOffice(),以定义去办公室工作的行为。

以下几点描述了方法在面向对象的世界中的作用。

下面是在Python v3.5中创建类和对象的一个例子:

class Person(object):
    def __init__(self, name, age):   #constructor
        self.name = name   #data members/ attributes
        self.age = age
    def get_person(self,):   # member function
        return "<Person (%s, %s)>" % (self.name, self.age)

p = Person("John", 32)   # p is an object of type Person
print("Type of Object:", type(p), "Memory Address:", id(p))

上述代码的输出结果如图1-1所示。

图1-1

现在我们已经了解了面向对象编程的基础知识,下面让我们深入了解面向对象编程的主要概念。

封装的主要特点如下所示。

多态的主要特征如下所示。

它为我们展示了Python内置类型的多态:

a = "John"
b = (1,2,3)
c = [3,4,6,8,9]
print(a[1], b[0], c[2])

以下几点有助于我们更好地理解继承过程。

在下面的代码示例中,类A是基类,类B继承了类A的特性。因此,类B的对象可以访问类A的方法:

class A:
    def a1(self):
        print("a1")

class B(A):
    def b(self):
        print("b")

 
b = B()
b.a1()

抽象的主要特征如下所示:

在下面的例子中,我们通过add ()方法对类Adder的内部细节进行了抽象处理:

class Adder:
    def __init__(self):
        self.sum = 0
    def add(self, value):
        self.sum += value

acc = Adder()
for i in range(99):
    acc.add(i)


print(acc.sum)

组合是指以下几点。

在下面的示例中,类A的对象被组合到了类B中:

class A(object):
    def a1(self):
        print("a1")

class B(object):
    def b(self):
        print("b")
        A().a1()


objectB = B()
objectB.b()

现在,让我们探讨另一组概念,这些概念对我们接下来的学习至关重要。

这些只是面向对象的设计原则,当我们深入细致地学习设计模式时,将作为工具箱使用。

开放/封闭原则规定,类或对象及其方法对于扩展来说,应该是开放的,但是对于修改来说,应该是封闭的。

简单地说,这意味着当你开发软件应用的时候,一定确保以通用的方式来编写类或模块,以便每当需要扩展类或对象行为的时候不必修改类本身。相反,类的简单扩展将有助于建立新的行为。

例如,开放/封闭原则能够在下列情形中表现得淋漓尽致:为了实现所需行为,用户必须通过扩展抽象基类来创建类的实现,而不是通过修改抽象类。

本设计原则的优点如下。

控制反转原则是指,高层级的模块不应该依赖于低层级的模块,它们应该都依赖于抽象。细节应该依赖于抽象,而不是抽象依赖于细节。

该原则建议任何两个模块都不应以紧密方式相互依赖。事实上,基本模块和从属模块应当在它们之间提供一个抽象层来耦合。这个原则还建议,类的细节应该描绘抽象。在某些情况下,这种观念会反转,也就是实现细节本身决定了抽象,这种情况是应该避免的。

控制反转原则的优点如下。

接口隔离原则规定,客户端不应该依赖于它们不需要使用的接口。

接口隔离原则的意思就是,软件开发人员应该仔细地处理接口。例如,它提醒开发人员/架构师开发的方法要与特定功能紧密相关。如果存在与接口无关的方法,那么依赖于该接口的类就必须实现它,实际上这是毫无必要的。

例如,一个Pizza接口不应该提供名为add_chicken()的方法。基于Pizza接口的Veg Pizza类不应该强制实现该方法。

本设计原则的优点如下所示。

单一职责的含义是:类的职责单一,引起类变化的原因单一。

这个原则是说,当我们开发类时,它应该为特定的功能服务。如果一个类实现了两个功能,那么最好将它们分开。也就是说,功能才是改变的理由。例如,一个类可以因为所需行为的变化而进行修改,但是如果一个类由于两个因素(基本上是两个功能的改变)而改变,那么该类就应该进行相应的分割。

本设计原则的优点如下所示。

替换原则规定,派生类必须能够完全取代基类。

这个原则很简单,当应用程序开发人员编写派生类时,该原则的含义就是他们应该扩展基类。此外,它还建议派生类应该尽可能对基类封闭,以至于派生类本身可以替换基类,而无需修改任何代码。

现在终于到了谈论设计模式的时候了!那么,什么是设计模式呢?

设计模式是由GoF(Gang of Four)首先提出的,根据他们的观点,设计模式就是解决特定问题的解决方案。如果你想进一步了解其定义,请参阅 Design Patterns: Elements of Reusable Object-Oriented Software一书,而GoF指的就是该书的4位作者。这本书的作者是Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides,前言由Grady Booch撰写。本书涵盖了软件设计方面常见问题的软件工程解决方案,书中提供了23种设计模式,并首次利用Java语言给出了程序实现。设计模式本身是一种发现,而不是一种发明。

设计模式的主要特点如下所示。

当你第一次听说设计模式时,可能会有以下联想。

你必须尝试解决设计模式想要解决的问题,也许你的解决方案并不完善,而我们所追求的完善性正是设计模式中固有的或隐含的。当我们提到完整性时,它可以指许多因素,例如设计、可扩展性、重用、内存利用率等。从本质上说,设计模式就是从别人的成功而非自己的失败中进行学习!

关于设计模式的另一个有趣的讨论是,什么时候使用它们?它是应用在软件开发生命周期(Software Development Life Cycle,SDLC)的分析或设计阶段吗?

有趣的是,设计模式是已知问题的解决方案。因此,设计模式在分析或设计阶段非常有用,并且如预期的那样,在开发阶段也非常有用,因为它们与应用的编程直接相关。

设计模式的优点如下所示。

不是每一段代码或每一种设计都可以叫作设计模式。例如,解决一个问题的编程构造或数据结构就不能被称为模式。下面让我们通过一种简单的方式来理解这些术语。

为了有效地使用设计模式,应用程序开发人员必须了解设计模式所适用的上下文。我们可以将上下文分为以下几种主要类型。

就像Lisp一样,Python也是一种动态语言。Python的动态特性如下所示。

GoF在他们的设计模式书中讲到了23种设计模式,并将它们分为三大类。

模式的分类主要基于对象的创建方式、软件应用程序中类和对象的构造方式,同时还涉及对象之间的交互方式。我们将在本节中详细介绍所有类型。

以下是创建型模式的性质。

单例模式是创建型模式的一个例子。

以下是结构型模式的性质。

适配器模式是结构型模式的一个例子。

行为型模式具有下列性质。

观察者模式是行为型模式的一个例子。

在本章中,我们介绍了面向对象编程的基本概念,如对象、类、变量,并通过示例代码解释了面向对象编程诸如多态、继承和抽象等特性。

然后,我们讲解了在设计应用程序时,作为开发人员或架构师应遵循的面向对象的设计原则。

接着,我们深入探讨了设计模式及其应用,同时,介绍了其适用的上下文和分类。

在阅读本章之后,读者就为将来进一步深入学习各种设计模式打下了牢固的基础。


在上一章中,我们探讨了设计模式及其分类。我们都知道,设计模式可以分三大类:结构型、行为型和创建型模式。

在这一章中,我们将学习单例设计模式。单例设计模式是应用开发过程中最简单和最著名的一种创建型设计模式。本章首先会对单例模式进行简要介绍,然后提供一个采用了该模式的实际例子,在Python代码示例的帮助下,对其进行深入的剖析。此外,本章还会介绍Monostate(或者Borg)设计模式,它是单例设计模式的一个变种。

在本章中,我们将会涉及以下主题:

在本章的结尾部分,我们将对单例模式进行简要总结。这将有助于读者针对单例设计模式的各个方面进行独立思考。

单例模式提供了这样一个机制,即确保类有且只有一个特定类型的对象,并提供全局访问点。因此,单例模式通常用于下列情形,例如日志记录或数据库操作、打印机后台处理程序,以及其他程序——该程序运行过程中只能生成一个实例,以避免对同一资源产生相互冲突的请求。例如,我们可能希望使用一个数据库对象对数据库进行操作,以维护数据的一致性;或者希望使用一个日志类的对象,将多项服务的日志信息按照顺序转储到一个特定的日志文件中。

简言之,单例设计模式的意图如下所示。

图2-1是单例模式的UML图。

图2-1

实现单例模式的一个简单方法是,使构造函数私有化,并创建一个静态方法来完成对象的初始化。这样,对象将在第一次调用时创建,此后,这个类将返回同一个对象。

在使用Python的时候,我们的实现方式要有所变通,因为它无法创建私有的构造函数。下面,我们一起看看如何利用Python语言来实现单例模式。

下面是基于Python v3.5的单例模式实现代码,它主要完成了两件事情。

1.只允许Singleton类生成一个实例。

2.如果已经有一个实例了,我们会重复提供同一个对象。

具体代码如下所示:

class Singleton(object):
     def __new__(cls):
       if not hasattr(cls, 'instance'):
         cls.instance = super(Singleton, cls).__new__(cls)
       return cls.instance
 

s = Singleton()
print("Object created", s)

s1 = Singleton()
print("Object created", s1)

图2-2是以上代码的输出结果。

图2-2

在上面的代码中,我们通过覆盖__new__方法(Python用于实例化对象的特殊方法)来控制对象的创建。对象s就是由__new__方法创建的,但在创建之前,该方法会检查对象是否已存在。

方法hasattr(Python的特殊方法,用来了解对象是否具有某个属性)用于查看对象cls是否具有属性instance,该属性的作用是检查该类是否已经生成了一个对象。当对象s1被请求的时候,hasattr()发现对象已经存在,所以,对象s1将被分配已有的对象实例(地址位于0x102078ba8)。

单例模式的用例之一就是懒汉式实例化。例如,在导入模块的时候,我们可能会无意中创建一个对象,但当时根本用不到它。懒汉式实例化能够确保在实际需要时才创建对象。所以,懒汉式实例化是一种节约资源并仅在需要时才创建它们的方式。

在下面的代码示例中,执行s = singleton()的时候,它会调用__init__方法,但没有新的对象被创建。然而,实际的对象创建发生在调用Singleton.getInstance()的时候,我们正是通过这种方式来实现懒汉式实例化的。

class Singleton:
    __instance = None
    def __init__(self):
        if not Singleton.__instance:
            print(" __init__ method called..")
        else:
            print("Instance already created:", self.getInstance())
    @classmethod
    def getInstance(cls):
        if not cls.__instance:
            cls.__instance = Singleton()
        return cls.__instance


s = Singleton() ## class initialized, but object not created
print("Object created", Singleton.getInstance()) # Object gets created 
here
s1 = Singleton() ## instance already created

默认情况下,所有的模块都是单例,这是由Python的导入行为所决定的。

Python通过下列方式来工作。

1.检查一个Python模块是否已经导入。

2.如果已经导入,则返回该模块的对象。如果还没有导入,则导入该模块,并实例化。

3.因此,当模块被导入的时候,它就会被初始化。然而,当同一个模块被再次导入的时候,它不会再次初始化,因为单例模式只能有一个对象,所以,它会返回同一个对象。

这里我们讨论的是 GoF(the Gang of Four,GoF)编写的设计模式图书中的“第1章,设计模式入门”中的相关内容。GoF(the Gang of Four,GoF)的单例设计模式是指,一个类有且只有一个对象。然而,根据Alex Martelli的说法,通常程序员需要的是让实例共享相同的状态。他建议开发人员应该关注状态和行为,而不是同一性。由于该概念基于所有对象共享相同状态,因此它也被称为Monostate(单态)模式。

Monostate模式可以通过Python轻松实现。在下面的代码中,我们将类变量__shared_state赋给了变量__dict__(它是Python的一个特殊变量)。Python使用__dict__存储一个类所有对象的状态。在下面的代码中,我们故意把__shared_state赋给所有已经创建的实例。所以,如果我们创建了两个实例“b”和“b1”,我们将得到两个不同的对象,这一点与单例模式大为不同,后者只能生成一个对象。然而,对象的状态,即b.__dict__b1.__dict__却是相同的。现在,就算对象b的对象变量x发生了变化,这个变化也会复制到被所有对象共享的__dict__变量,即b1的变量x的值也会从1变为4。

class Borg:
    __shared_state = {"1":"2"}
    def __init__(self):
        self.x = 1
        self.__dict__ = self.__shared_state
        pass

b = Borg()
b1 = Borg()
b.x = 4

print("Borg Object 'b': ", b) ## b and b1 are distinct objects
print("Borg Object 'b1': ", b1)
print("Object State 'b':", b.__dict__)## b and b1 share same state
print("Object State 'b1':", b1.__dict__)

图2-3是以上代码的输出。

图2-3

除此之外,我们还可以通过修改__new__方法本身来实现Borg模式。我们知道,__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

让我们先来了解一下元类。元类是一个类的类,这意味着该类是它的元类的实例。使用元类,程序员有机会从预定义的Python类创建自己类型的类。例如,如果你有一个对象MyClass,你可以创建一个元类MyKls,它按照你需要的方式重新定义MyClass的行为。下面,让我们来深入介绍它们。

在Python中,一切皆对象。如果我们说a=5,则type(a)返回<type'int'>,这意味着aint类型。但是,type(int)返回<type'type'>,这表明存在一个元类,因为inttype类型的类。

类的定义由它的元类决定,所以当我们用类A创建一个类时,Python通过A=type(name,bases,dict)创建它。

现在,如果一个类有一个预定义的元类(名为Metals),那么Python就会通过A=MetaKls(name,bases,dict)来创建类。

让我们看看在Python 3.5中的一个示例元类的实现:

class MyInt(type):
    def __call__(cls, *args, **kwds):
        print("***** Here's My int *****", args)
        print("Now do whatever you want with these objects...")
        return type.__call__(cls, *args, **kwds)

class int(metaclass=MyInt):
    def __init__(self, x, y):
        self.x = x
        self.y = y

i = int(4,5)

图2-4是上述代码的输出。

图2-4

对于已经存在的类来说,当需要创建对象时,将调用Python的特殊方法__call__。在这段代码中,当我们使用int(4,5)实例化int类时,MyInt元类的__call__方法将被调用,这意味着现在元类控制着对象的实例化。

前面的思路同样适用于单例设计模式。由于元类对类创建和对象实例化有更多的控制权,所以它可以用于创建单例。(注意:为了控制类的创建和初始化,元类将覆盖__new____init__方法。)

以下示例代码能够更好地帮我们解释基于元类的单例实现:

class MetaSingleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MetaSingleton, \
                cls). __call__(*args, **kwargs)
        return cls._instances[cls]

class Logger(metaclass=MetaSingleton):
    pass

logger1 = Logger()
logger2 = Logger()
print(logger1, logger2)

作为一个实际的用例,我们将通过一个数据库应用程序来展示单例的应用。这里不妨以需要对数据库进行多种读取和写入操作的云服务为例进行讲解。完整的云服务被分解为多个服务,每个服务执行不同的数据库操作。针对UI(Web应用程序)上的操作将导致调用API,最终产生相应的DB操作。

很明显,跨不同服务的共享资源是数据库本身。因此,如果我们需要更好地设计云服务,必须注意以下几点。

这里提供了一个示例Python实现:

import sqlite3 
class MetaSingleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(MetaSingleton, \
                cls). __call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=MetaSingleton):
  connection = None
  def connect(self):
    if self.connection is None:
        self.connection = sqlite3.connect("db.sqlite3")
        self.cursorobj = self.connection.cursor()
    return self.cursorobj

db1 = Database().connect()
db2 = Database().connect()

print ("Database Objects DB1", db1)
print ("Database Objects DB2", db2)

上面代码的输出如图2-5所示。

图2-5

通过阅读上面的代码,我们会发现以下几点。

1.我们以MetaSingleton为名创建了一个元类。就像在上一节中解释的那样,Python的特殊方法__call__可以通过元类创建单例。

2.数据库类由MetaSingleton类装饰后,其行为就会表现为单例。因此,当数据库类被实例化时,它只创建一个对象。

3.当Web应用程序对数据库执行某些操作时,它会多次实例化数据库类,但只创建一个对象。因为只有一个对象,所以对数据库的调用是同步的。此外,这样还能够节约系统资源,并且可以避免消耗过多的内存或CPU资源。

假如我们要开发的不是单个Web应用程序,而是集群化的情形,即多个Web应用共享单个数据库。当然,单例在这种情况下好像不太好使,因为每增加一个Web应用程序,就要新建一个单例,添加一个新的对象来查询数据库。这导致数据库操作无法同步,并且要耗费大量的资源。在这种情况下,数据库连接池比实现单例要好得多。

让我们考虑另一种情况,即为基础设施提供运行状况监控服务(就像Nagios所作的那样)。我们创建了HealthCheck类,它作为单例实现。我们还要维护一个被监控的服务器列表。当一个服务器从这个列表中删除时,监控软件应该觉察到这一情况,并从被监控的服务器列表中将其删除。

在下面的代码中,hc1hc2对象与单例中的类相同。

我们可以使用addServer()方法将服务器添加到基础设施中,以进行运行状况检查。首先,通过迭代对这些服务器的运行状况进行检查。之后,changeServer()方法会删除最后一个服务器,并向计划进行运行状况检查的基础设施中添加一个新服务器。因此,当运行状况检查进行第二次迭代时,它会使用修改后的服务器列表。

所有这一切都可以借助单例模式来完成。当添加或删除服务器时,运行状况的检查工作必须由了解基础设施变动情况的同一个对象来完成:

class HealthCheck:
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not HealthCheck._instance:
            HealthCheck._instance = super(HealthCheck, \
                cls). __new__(cls, *args, **kwargs)
        return HealthCheck._instance
    def __init__(self):
        self._servers = []
    def addServer(self):
        self._servers.append("Server 1")
        self._servers.append("Server 2")
        self._servers.append("Server 3")
        self._servers.append("Server 4")
    def changeServer(self):
        self. _ servers.pop()
        self._servers.append("Server 5")

hc1 = HealthCheck()
hc2 = HealthCheck()

hc1.addServer()
print("Schedule health check for servers (1)..")
for i in range(4):
    print("Checking ", hc1._servers[i])

hc2.changeServer()
print("Schedule health check for servers (2)..")
for i in range(4):
    print("Checking ", hc2. _ servers[i]) 

代码的输出如图2-6所示。

图2-6

虽然单例模式在许多情况下效果很好,但这种模式仍然存在一些缺陷。由于单例具有全局访问权限,因此可能会出现以下问题。

 

提示: 

在本章中,我们学习了关于单例的许多内容。对于单例模式来说,以下几点需要牢记。

  • 在许多实际应用程序中,我们只需要创建一个对象,如线程池、缓存、对话框、注册表设置等。如果我们为每个应用程序创建多个实例,则会导致资源的过度使用。单例模式在这种情况下工作得很好。

  • 单例是一种经过时间考验的成熟方法,能够在不带来太多缺陷的情况下提供全局访问点。

  • 当然,该模式也有几个缺点。当使用全局变量或类的实例化非常耗费资源但最终却没有用到它们的情况下,单例的影响可以忽略不计。

在本章中,我们介绍了单例设计模式及其应用的上下文。我们知道,当要求一个类只有一个对象时,就可以使用单例模式。

我们还研究了利用Python实现单例模式的各种方法。对于经典的实现方式来说,允许进行多次实例化,但返回同一个对象。

我们还讨论了Borg或Monostate模式,这是单例模式的一个变体。Borg允许创建共享相同状态的多个对象,这与GoF描述的单例模式有所不同。

之后,我们继续探讨了Web应用程序,其中单例模式可以用于在多个服务间实现一致的数据库操作。

最后,我们还研究了单例可能出现的错误,以及开发人员需要避免的情况。

在本章内容的基础上,读者就可以顺利研究其他创建型模式并从中获益了。

在下一章中,我们将考察其他创建型模式和工厂设计模式,介绍工厂方法和抽象工厂模式,并通过Python的代码示例来加深理解。


相关图书

深度学习的数学——使用Python语言
深度学习的数学——使用Python语言
动手学自然语言处理
动手学自然语言处理
Web应用安全
Web应用安全
Python高性能编程(第2版)
Python高性能编程(第2版)
图像处理与计算机视觉实践——基于OpenCV和Python
图像处理与计算机视觉实践——基于OpenCV和Python
Python数据科学实战
Python数据科学实战

相关文章

相关课程