《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中实现起来非常简单。设计模式不仅可用于创建各种简单及复杂的对象,而且还能指导对象的行为,也就是规定单个对象或一组对象应该怎样完成其工作。下一章就要讲解这些“行为型设计模式”。

相关文章
|
1月前
|
Python
如何使用Python编写一个简单的计算器程序
如何使用Python编写一个简单的计算器程序
37 0
|
1月前
|
算法 安全 调度
解决Python并发访问共享资源引起的竞态条件、死锁、饥饿问题的策略
解决Python并发访问共享资源引起的竞态条件、死锁、饥饿问题的策略
25 0
|
1月前
|
Linux 数据安全/隐私保护 iOS开发
python如何将程序编译成exe
python如何将程序编译成exe
32 0
|
30天前
|
存储 算法 数据处理
使用Python编写高效的数据处理程序
在当今信息爆炸的时代,数据处理变得越来越重要。本文将介绍如何使用Python语言编写高效的数据处理程序,包括利用Python内置的数据结构和函数、优化算法和并行处理等技术,帮助开发者更好地处理和分析大规模数据。
|
17天前
|
分布式计算 算法 搜索推荐
优化 Python 程序的五大技巧
本文介绍了优化 Python 程序的五大技巧,涵盖了代码结构优化、算法选择、内置函数利用、库的使用以及并行处理等方面。通过对这些技巧的实践,可以提升 Python 程序的性能和效率,从而更好地满足各类应用的需求。
|
29天前
|
存储 数据库连接 数据处理
Python语言的程序框架
Python语言的程序框架
|
1月前
|
设计模式 uml
设计模式之代理模式
设计模式之代理模式
|
1月前
|
SQL 安全 测试技术
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
15 0
|
1月前
|
存储 安全 数据安全/隐私保护
什么是 Web 应用程序的会话管理?如何在 Python 中实现?
什么是 Web 应用程序的会话管理?如何在 Python 中实现?
10 2
|
1月前
|
Python Perl
情人节到了,写一份爱心程序(python)
情人节到了,写一份爱心程序(python)
27 0