《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.7 代理模式

简介:

本节书摘来自华章出版社《Python编程实战:运用设计模式、并发和程序库创建高质量程序》一 书中的第2章,第2.7节,作者:(美) Mark Summerfield,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.7 代理模式

若想用一个对象来代表另一个对象,则可使用“代理模式”(Proxy Pattern)。《Design Patterns》一书举了四个用例。第一个用例是“远程代理”(remote proxy):用本地对象来代表远程对象。RPyC程序库就是个很好的例子,它可以在服务器端创建对象,并在一台或多台客户端中创建针对这些对象的代理(6.2节将会介绍这个程序库)。第二个用例是“虚代理”(virtual proxy),用来创建能够代表复杂对象的轻量级对象,只在确有必要时才会真正去创建那个复杂对象。本节所举的例子就是这种代理。第三个用例是“保护代理”(protection proxy),可根据客户端的访问权限来确定不同的访问级别。最后一种用例是“智能引用”(smart reference),可用来在“访问对象时执行额外操作”(performs additional actions when an object is accessed)。这些代理模式都可以采用同一套编码方式来实现,其中第四种代理还可以通过描述符来实现(比方说,利用@property修饰器,以属性来取代普通对象)。
代理模式也可用于单元测试。例如,受测代码所需访问的资源并非随时可用,或是所需使用的类尚未开发完毕而依然不完整,那就可以考虑为资源或类创建代理,令代理对象提供所有接口,并且用“桩”(stub)来表示那些缺失的功能。这种做法非常有用,Python 3.3包含了unittest.mock库,可用来创建“模拟对象”(mock object),并设置“桩”来表示缺失的方法。
本节范例所假定的使用场景是:我们可能会创建很多图像,但最后只会用到其中一张。Image模块与功能相仿但速度更快的cyImage模块都可以创建图像(第3.12节及5.3节分别讲解二者),但它们一开始就会把图像创建在内存里。而我们只会用到这些图像中的一张,所以更好一些的办法是:创建许多轻量级图像代理,然后只在真正有需要时才去创建实际图像。
除了构造器之外,Image.Image类的接口还有十个方法:load()、save()、pixel()、set_pixel()、line()、rectangle()、ellipse()、size()、subsample()、scale()。(此外,还有一些静态的便捷方法以及等效的模块函数,例如Image.Image.color_for_name()及Image.color_for_name()。)
代理类只需要实现Image.Image中我们必须用到的那些方法即可。首先来看代理类的用法。本节范例代码选自imageproxy1.py,绘制出的图像如图2.8所示。

b41289dcf127774f1b5e971a0e6fbef31ccf5a92

首先,需要用Image模块的color_for_name()函数创建一些颜色常量。
screenshot

上面这段代码先创建了ImageProxy对象,我们在创建时把需要使用的Image类传给了构造器。然后又在对象上面绘制了一些内容,最后将绘制好的图像存储起来。假如创建图像时调用的不是ImageProxy()而是Image.Image(),那么剩下的绘制操作依然能照常执行。但是,采用了图像代理之后,只有在调用save()方法时才会去创建真正的图像,这样的话,在执行保存操作之前,创建图像的开销(无论是内存开销还是处理开销)就变得非常小,若是最后不保存图像而直接将其丢弃,那么损失也会很低。若用Image.Image来创建,则一开始就需要很大开销(也就是说,一开始就要创建大小为width×height的数组用以保存颜色值),而且在绘制时还需要执行很多处理工作(例如在填充矩形时,要计算出需要填充的像素,并把它们的颜色都设置好),即便最后决定要丢弃这张图像,也还是得执行丢弃之前那些操作。
screenshot

只要ImageProxy所提供的接口足够用,它就能代表Image.Image(如果构造时传入了支持Image接口的其他类,那么也能代表那个类)。ImageProxy并不保存图像,它保存的是一份元组列表,每个元组表示一条命令,其首个元素是函数或非绑定方法,其余元素是传给调用函数或方法的参数。
创建ImageProxy对象时,必须指定长和宽(以便按此大小来新建图像)或文件名。如果用文件名创建ImageProxy,那么就会保存一条命令,这条命令旨在调用Image.Image()构造器,构造器所用的width及height参数都是None,而filename参数则是创建ImageProxy时所传入的文件名,ImageProxy.load()方法所对应的命令与此相同。创建好ImageProxy对象之后,如果又调用了ImageProxy.load()方法,那么先前的全部命令都将丢弃,self.commands命令列表中只会留下一条新建图像的命令。若用给定的长度与宽度来创建ImageProxy对象,则对应的命令中保存的是Image.Image()构造器,构造器所用的width及height参数是创建时所传入的长度与宽度。
如果调用了代理对象所不支持的方法(比如pixel()),那么Python就会发现这个方法找不到,从而自动抛出AttributeError,而这正是我们想要的效果。还有一种处理办法:如果代理对象不支持将要调用的方法,那就把实际的Image对象创建出来,并在此对象上执行后续操作。(imageproxy2.py程序采用这种办法,该程序的代码没有列在本节中。)
screenshot
screenshot

Image.Image类的接口中有四个绘制方法:line()、rectangle()、ellipse()、set_pixel()。我们的ImageProxy类完全支持这些方法,但并不当场执行操作,而是把操作及其参数做成一条命令,放在self.commands列表里。
screenshot

只有在保存时才需要创建真正的图像,也只有此时才会有真正的处理开销及内存开销。ImageProxy的设计方式决定了其首个命令一定是新建图像(可能是根据长宽来创建,也可能是从既有文件中加载)。所以我们采用特殊方式来处理第一条命令:将执行该命令所得的返回值保存起来,这个返回值肯定是个Image.Image或cyImage.Image。然后,遍历剩下的命令,并依次执行之,由于执行的都是非绑定方法,所以需要把image变量作为首个参数(也就是self)传进去。最后,调用Image.Image.save()方法,保存图像。
虽说Image.Image.save()方法在发生错误时会抛出异常,但这个方法本身是没有返回值的。然而ImageProxy的save()方法却稍有不同,它会把创建好的Image.Image对象返回给调用者,以备后续处理时所需。这样修改应该不会出问题,因为假如调用者不使用返回值的话(比如调用Image.Image.save()方法时,我们就没打算使用返回值),那么Python就会将其直接丢弃。imageproxy2.py程序无须像这样修改,因为它有个类型为Image.Image的image属性可供访问,如果访问时图像尚未创建,那么会当场创建一份。
像本例这样把命令存储起来,可以为实现“执行-撤销”(do-undo)功能做准备,这一话题请参考3.2节的命令模式以及3.8节的状态模式。
结构型设计模式都可以用Python语言实现出来。适配器模式与外观模式能够把已有的类放在新环境下重新使用,而桥接模式则可以把某个类里的复杂功能嵌入另一个类中。组合模式可以非常方便地创建出对象层次结构,但Python中却很少用到它,因为采用dict就可以实现相同的功能了。修饰器模式特别有用,Python语言对此提供了原生支持,我们还可以用修饰器来修饰类。Python的对象引用机制可以视为享元模式的变种。代理模式在Python中实现起来非常简单。设计模式不仅可用于创建各种简单及复杂的对象,而且还能指导对象的行为,也就是规定单个对象或一组对象应该怎样完成其工作。下一章就要讲解这些“行为型设计模式”。

相关文章
|
3天前
|
Linux Python Windows
打包Python程序文件:pyinstaller实现
本文介绍基于Python语言中的pyinstaller模块,将写好的.py格式的Python代码及其所用到的所有第三方库打包,生成.exe格式的可执行文件,从而方便地在其他环境、其他电脑中直接执行这一可执行文件的方法。
|
4天前
|
机器学习/深度学习 网络协议 数据库
Python编程实战:解决常见编程问题
```markdown Python编程入门指南:涵盖文件操作、列表操作、字符串处理、函数编写、异常处理、网络编程和数据库操作等实战案例。通过示例代码,学习如何读写文件、排序列表、转换字符串、创建函数、处理异常、构建TCP服务器及操作SQLite数据库,逐步掌握Python核心技能。 ```
|
4天前
|
数据采集 JSON 数据库
800个程序实例、5万行代码!清华大学出版【Python王者归来】
Python 的丰富模块(module)以及广泛的应用范围,使 Python 成为当下最重要的计算机语言之一,本书尝试将所有常用模块与应用分门别类组织起来,相信只要读者遵循本书实例,定可以轻松学会 Python 语法与应用,逐步向 Python 高手之路迈进,这也是撰写本书的目的。 本书以约 800 个程序实例讲解了:完整的 Python 语法,Python 的输入与输出,Python 的数据型态,列表(list)、元组(tuple)、字典(dict)、集合(set),函数设计,类别设计,使用系统与外部模块(module),设计自己的模块(module),文件压缩与解压缩,程序除错与异常处理…
|
7天前
|
数据库连接 Python
如何提高python程序代码的健壮性
在编程的时候,我们难免会遇到一些不可靠的情况,比如网络请求失败,数据库连接超时等等。这些不确定性会让我们的程序容易出现各种错误和异常。那么如何来增加程序的容错性和健壮性呢? 可能大多数人会想到使用try except来进行异常捕捉进行失败重试(Retry)。虽然try-escept一个非常常见和有效的方式来增强程序稳定性,但是可能一不小心就会造成栈溢出。 所以接下来我就来介绍一个另外的一个专门用于失败重试的库:retrying。
|
7天前
|
存储 算法 Python
Python编程作业一:程序基本流程
Python编程作业一:程序基本流程
14 0
|
7天前
|
网络协议 Python
在python中利用TCP协议编写简单网络通信程序,要求服务器端和客户端进行信息互传。 - 蓝易云
在这个示例中,服务器端创建一个socket并监听本地的12345端口。当客户端连接后,服务器发送一条欢迎消息,然后关闭连接。客户端创建一个socket,连接到服务器,接收消息,然后关闭连接。
62 0
|
8天前
|
数据采集 安全 数据挖掘
2024年最新7 年 Python 的我,总结了这 90 条写 Python 程序的建议,上海大厂Python面试经历
2024年最新7 年 Python 的我,总结了这 90 条写 Python 程序的建议,上海大厂Python面试经历
2024年最新7 年 Python 的我,总结了这 90 条写 Python 程序的建议,上海大厂Python面试经历
|
8天前
|
Python
2024年最新【Python】程序的组织结构:顺序结构,2024年最新46道面试题带你了解中高级Python面试
2024年最新【Python】程序的组织结构:顺序结构,2024年最新46道面试题带你了解中高级Python面试
2024年最新【Python】程序的组织结构:顺序结构,2024年最新46道面试题带你了解中高级Python面试
|
8天前
|
机器学习/深度学习 数据采集 数据挖掘
90%的人说Python程序慢,5大神招让你的代码像赛车一样跑起来_代码需要跑很久怎么办(2)
90%的人说Python程序慢,5大神招让你的代码像赛车一样跑起来_代码需要跑很久怎么办(2)
|
9天前
|
机器学习/深度学习 数据采集 Java
如何提高Python程序的性能
Python作为一种高级编程语言,具有易学易用、开发效率高等优点,但其在性能上可能不如C++或Java。本文将介绍如何通过一些技巧和工具来提高Python程序的性能。