课程简介

“读万卷书不如走万里路”,设计模式学不好的根本,还是在于缺少实战

新课《Python 设计模式基础实战》向大家介绍了编程语言的设计模式,着重强调大家动手实际操作。

总苦于无法将设计模式活学活用的你,不妨来学一下。

设计模式简介

所谓“设计模式”就是一套由前人总结的代码的设计思路。以其中最常用的“单例模式”为例,在程序中我们有一个需求,这个需求的实现有多种思路,其中一种是创建一个类并且使得该类在多次实例化时生成唯一的一个实例。这就需要设计代码实现这个结果。大家发现这种场景下这样设计是最合理的,我们就管这种设计思路叫做“单例模式”。

设计模式不分语言,大多数编程语言都可以实现。

一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码。拿建造桥梁粗略地类比一下,一座桥是一个小功能,代码就是砖石瓦块钢筋水泥,设计模式就是我们要怎么建造,圆拱桥、独木桥、管道桥还是拉索桥。

设计模式很有用,但它要用到合适的场景中才能发挥应有的效果,否则可能出现弊大于利的情况。

通常来讲设计模式分为三类:

  • 创建模式,提供实例化的方法,为适合的状况提供相应的对象创建方法。
  • 结构模式,通常用来处理实体之间的关系,使得这些实体能够更好地协同工作。
  • 行为模式,用于在不同的实体建进行通信,为实体之间的通信提供更容易、更灵活的通信方法。

单例模式

所谓单例模式,也就是说任何时候我们都要确保只有一个对象实例存在。很多情况下,整个系统中只需要存在一个对象,所有的信息都从这个对象获取,比如系统的配置对象,或者是线程池。这些场景下,就非常适合使用单例模式。

总结起来,就是说不管我们实例化一个类多少次,真正干活的对象只会生成一次并且在首次实例化时生成。

在 Python 中实现单例模式的方式有很多,下面分别举例说明。

1)使用嵌套类实现

在定义类 A 时,在类中再定义一个嵌套类 _A。首次对类 A 进行实例化时,将类 _A 的实例赋值给类 A 的属性 _instance ,然后给类 A 的实例定义一个 __getattr__ 方法,使得类 A 的实例调用自身属性或方法时,都去调用类的 _instance 属性,也就是类 _A 的实例的属性和方法。类属性是固定不变的,所以类 A 的实例虽然是不同的,但它们的属性和方法都是完全一样的。

将如下代码写入 singleton_1.py 文件中:

class Singleton:'''单例模式'''# 创建一个嵌套类class _A:def display(self):    # 1return id(self)_instance = Nonedef __init__(self):    # 2__class__._instance = __class__._instance or __class__._A()def __getattr__(self, attr):    # 3return getattr(__class__._instance, attr)def __setattr__(self, attr, value):    # 4object.__setattr__(__class__._instance, attr, value)if __name__ == '__main__':s1 = Singleton(); s2 = Singleton()    # 5print('id(s1):', id(s1))    # 6print('id(s2):', id(s2))print('s1.display():', s1.display())    # 7print('s2.display():', s2.display())s1.name = 'James'    # 8print('s1.name:', s1.name)print('s2.name:', </pre>

如上所示,对代码进行简略说明:

  • # 1 创建一个嵌套类 _A,在类内部定义一个 display 方法,该方法返回 _A 类的实例的内存地址。
  • # 2 编写 Singleton 类的实例的初始化方法。创建 Singleton 类的实例后,执行此方法。方法内部是对类的操作,为类的 _instance 属性赋值一个 _A 的实例。第一次对 Singleton 进行实例化时会创建一个 _A 类的实例并赋值,以后不再变化。
  • # 3 编写 Singleton 类的实例获取属性的方法。Singleton 类内部故意不为自身的实例设置任何属性,结果就是调用实例的属性时最后落到此方法的头上。方法内部获取类属性 _instance 的同名属性,也就是 _A 类的实例的属性。
  • # 4 编写 Singleton 类的实例定义属性的方法。同样,此方法内部调用 object.__setattr__ 方法为 Singleton._instance 也就是 _A 的实例定义属性。
  • # 5 为 Singleton 类创建两个实例以备测试。
  • # 6 打印两个实例的内存地址,它们的结果应该是不同的。
  • # 7 打印两个实例调用 display 方法的结果,实际上调用的都是 Singleton._instance 的同名方法,结果应该是一样的。
  • # 8 其中一个实例定义 name 属性,然后两个实例获取该属性并打印,结果应该都是一样的。

终端执行脚本,操作结果如下所示:

$ python3 singleton_1.py
id(s1): 4330824208
id(s2): 4330824336
s1.display(): 4330824272
s2.display(): 4330824272
s1.name: James
s2.name: James

如上所示,Singleton 的实例各不相同,它们在赋值属性和调用属性时,结果却是相同的。因为这些实例操作属性时都转移到了嵌套类 _A 的实例上。

2)使用装饰器实现

以上代码虽然很好地实现了单例模式,但是在真正的项目开发中这种方式却不够灵活,因为我们要将真正干活的类内置在单例类中,这会有些麻烦,例如删除实例的属性这一点就不太好实现。

下面我们使用 Python 装饰器来实现单例模式:

首先创建一个「类装饰器」,也就是编写一个类,这个类作为装饰器。这个「类装饰器」在实例化的时候,将另一个类作为参数。类装饰器的名字是 SingletonDeco ,其 __call__ 方法就是实例调用自身所执行的方法,我们可以把此方法的返回值定义为唯一的对象。

将如下代码写入 singleton_2.py 文件中:

class SingletonDeco:"""单例类装饰器"""def __init__(self, cls):    # 1print('装饰器初始化')self._cls = clsdef instance(self):    # 2try:return self._instanceexcept AttributeError:self._instance = self._cls()return self._instancedef __call__(self):    # 3return self.instance()@SingletonDeco    # 4
class Singleton:def display(self):return id(self)if __name__ == '__main__':s1 = Singleton()    # 5s2 = Singleton()print('id(s1):', s1.display())print('id(s2):', s2.display())print('s1 is s2:', s1 is s2)

对代码进行简单描述:

  • # 1 类装饰器的初始化方法,将被装饰的类赋值给实例的 _cls 属性。
  • # 2 此方法用于给实例的 _instance 属性赋值,此方法的调用权交个了 __call__ 方法,也就是说调用 SingletonDeco 类的实例时会执行 instance 方法并返回被装饰器装饰的类 Singleton 的实例。并且不论调用多少次,结果都是一样的。
  • # 3 类装饰器 SingletonDeco 的实例的调用接口。
  • # 4 使用类装饰器创建 Singleton 类,创建该类时,会执行 SingletonDeco.__init__ 方法,并且将该类赋值给实例的 _cls 属性。此时 Singleton 这个变量就指向了 SingletonDeco 这个类的实例。如果要获取原 Singleton 类,就需要调用 Singleton 的 _cls 属性。此外原 Singleton 类为实例提供了 display 方法返回实例的内存地址。
  • # 5 调用 Singleton ,表面上看是对 Singleton 类进行实例化,实际上是调用 SingletonDeco 类的实例的 __call__ 方法。因为变量 Singleton 指向的就是 SingletonDeco 的实例。调用 __call__ 的结果就是调用 instance 方法,下一步就是对调用实例的 _cls 属性,而这个属性的值就是原 Singleton 类。综上所述,这个最终的调用结果还是原 Singleton 的实例。绕这么大一圈,一切都是为了在 instance 方法中实现唯一实例。

终端执行结果如下:

$ python3 singleton_2.py
装饰器初始化
id(s1): 4350000976
id(s2): 4350000976
s1 is s2: True

以上代码中,我们用装饰器实现了单例模式,任何想使用单例模式的类,只需要使用 Singleton 装饰器装饰一下就可以使用了。

可以看到其核心工作原理其实和第一种实现方式是一致的,也是使用内置的属性 Singleton._instance 来存储实例的。通过使用装饰器的模式我们将代码解耦了,使用更加灵活。

其实这里我们也用到装饰者模式啦,后面的章节会介绍。

3)重写 new 方法

在对类进行实例化时,需要先调用类的 __new__ 方法创建实例,再调用实例的 __init__ 方法初始化。所以要实现单例模式,可以在类的 __new__ 方法中做文章。

首先判断类属性 __instance 是否存在。注意,使用 hasattr 方法时,相当于在类的外部调用类属性,私有属性的命令是一个下划线加类名加属性名。如果该属性不存在,调用根父类 object 的 __new__ 生成一个 Singleton 类的实例并赋值给类属性 __instance 。接下来打印类属性 __instance 的内存地址,实际上就是类的实例的内存地址。最后返回该实例。

在对类进行实例化时,只有首次会创建类的实例,之后都是返回类的 __instance 属性值。这样设计就可以实现单例模式了。

将如下代码写入 singleton_3.py 文件:

class Singleton:def __new__(cls, *args, **kw):if not hasattr(cls, '_Singleton__instance'):cls.__instance = super().__new__(cls, *args, **kw)print('实例化时打印实例 ID:', id(cls.__instance))return cls.__instances1 = Singleton()
s2 = Singleton()print('s1 is s2:', s1 is s2)

其中 super().__new__ 等同于 object.__new__

如果不重写类的 __new__ 方法,则默认调用 object 的同名方法并返回,也就是每次都会创建一个新的实例。重写的目的就是将一个实例固定到类属性中,然后每次创建实例时都返回这个属性值。这个方式思路简单,代码也很清晰。

终端执行程序结果如下:

$ python3 singleton_3.py
实例化时打印实例 ID: 4480060240
实例化时打印实例 ID: 4480060240
s1 is s2: True

总结

本节实验内容来自于《Python 设计模式基础实战》,主要介绍了设计模式的基本概念,并对创建型模式之一单例模式的实现进行了讲解,其中涉及到三个方法,它们可以根据实际场景进行恰当地选用。所有的设计都是为了实现对某个类的实例进行唯一的限制。

后续实验我们将讲解其他模式,以及一些额外的关于“元类”的知识等。

你将学到:

点击《Python 设计模式基础实战》,学习完整课程。

Python丨为什么你学不好设计模式?相关推荐

  1. 英语不好怎么自学python_为什么我就是学不好英语啊?我明明很努力,但是为... 我英语一般,但我很想学Python这个编程语言,行不?...

    导航:网站首页 > 为什么我就是学不好英语啊?我明明很努力,但是为... 我英语一般,但我很想学Python这个编程语言,行不? 为什么我就是学不好英语啊?我明明很努力,但是为... 我英语一般 ...

  2. 你为什么学不好Python?

    为什么有的人能学好 Python 而有的人就学不好? 想请问大家扪心自问一些之前有自学过 Python 么?如果学过那大家思考一下为什么没学好呢? 只是 Python 没学好,还是学什么都这样呢?甚至 ...

  3. python在房地产中的应用_“人生苦短,我学 Python”丨爆火的Python语言应用领域主要有哪些?...

    原标题:"人生苦短,我学 Python"丨爆火的Python语言应用领域主要有哪些? 「人生苦短,我学 Python」是众多和计算机科学有些接触的同学耳熟能详的段子,不过最近这一风 ...

  4. 数学不好python好学吗_数学不好的人,是否还应该坚持学编程?

    翻译如下:"火车在凌晨3:00离开纽约,平均每小时30英里.另一列朝同一方向的火车在上午6:00离开纽约,平均每小时60英里.在第二班火车离开多少小时后,它会遇到第一列火车?" 你 ...

  5. 英语不好可以学python_想学Python这个,英语基础不好,可以学会吗?

    展开全部 相信在人们的印象2113中,只要是IT类的编程全都是英文5261字母,这使得很多不懂英4102语的人望而生畏,担心自己学不会1653,从而放弃,随着人工智能的不断发展,很多小伙伴想要学习,但 ...

  6. 没学过编程可以自学python吗-完全没学过编程的人学习 Python前应该掌握些什么?...

    在众多高大上的自学指导中,尝试做一股清流,把要讲清楚的都讲清楚,除了一堆资料之外,你能在学之前就有一个非常明显的结果倾向. 本文以<小白带你学Python>为内容方向,试图在繁杂的信息里, ...

  7. 软件工程python就业方向-月薪2万+的Python Web岗,学到什么程度能找到工作?

    原标题:月薪2万+的Python Web岗,学到什么程度能找到工作? 学Python Web开发框架到什么程度可以找到开发的工作? 做出一个什么样的网站?看懂框架的源码? 今天我们来看看2位过来人的回 ...

  8. 学python要多少钱-学python去培训班要多少钱?

    人工智能的普及推动了python语言的普及.我们都非常清楚python的前景.语言优势.优厚的薪水和福利等,除了这些众所周知的福利外,从人工智能的角度来看,你会发现python的优越地位确实名副其实. ...

  9. 要不要学Python?如何快速学Python?

    随着Python的技术的流行,Python为人们的工作与生活上带来了很多的便捷.因为Python简单,学起来快,也是不少新手程序员入门的首选语言.但有几个问题是想入门Python的小伙伴所关心的:要不 ...

最新文章

  1. 调研字节码插桩技术,用于系统监控设计和实现
  2. logstash grok匹配
  3. 使用script命令自动录屏用户操作
  4. 如何给 Visual C++ 中的对话框增加位图背景
  5. python如何获取文件的行号_Python当我捕获异常时,如何获取类型,文件和行号?...
  6. 根据id获取多维数组路径_clickhouse数据模型之用户路径分析
  7. 为什么选择Bootstrap
  8. VC下使用Proc连接Oracle数据库
  9. java 分词词频_利用word分词来对文本进行词频统计
  10. hadoopstreaming
  11. linux fcntl函数,Linux C 学习之 - fcntl 函数
  12. McObject为风河航空电子平台提供实时数据管理能力
  13. 如何解决端口冲突的问题???
  14. css3实现椭圆轨迹运动
  15. Python 断言的使用
  16. 用c语言编写人机结合的加法,综合集成研讨厅中人机结合的研讨流程研究.pdf
  17. c++ 让程序玩贪吃蛇游戏
  18. 分享一款上班摸鱼神器,再也不怕领导突然出现在身后了~
  19. jQuery的下载与基本使用
  20. 6-vulnhub靶场-LordOfTheRoot_1.0.1靶机内核提权udf提权缓冲区溢出提权

热门文章

  1. js 判断是否是IE浏览器及ie版本
  2. 如何利用express新建项目(上)
  3. 关于Adium近期无法添加MSN联系人的说明
  4. C'mon C'mon-Von Bondies
  5. docker+httpd的安装
  6. net clr via c sharp chap1-- note
  7. hdu 3367 Pseudoforest (最大生成树 最多存在一个环)
  8. 【洛谷P1632】点的移动
  9. 在asp.net中如何自己编写highcharts图表导出到自己的服务器上来
  10. C#生成Excel报表 用MyXls组件生成更完美