本节书摘来自华章出版社《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所示。

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

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

只要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程序采用这种办法,该程序的代码没有列在本节中。)

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

只有在保存时才需要创建真正的图像,也只有此时才会有真正的处理开销及内存开销。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中实现起来非常简单。设计模式不仅可用于创建各种简单及复杂的对象,而且还能指导对象的行为,也就是规定单个对象或一组对象应该怎样完成其工作。下一章就要讲解这些“行为型设计模式”。

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

  1. 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 第1章 Python的创建型设计模式...

    本节书摘来自华章出版社<Python编程实战:运用设计模式.并发和程序库创建高质量程序>一 书中的第1章,第1.1节,作者:(美) Mark Summerfield,更多章节内容可以访问云 ...

  2. python经典书籍:Python编程实战 运用设计模式、并发和程序库创建高质量程序

    Python编程实战主要关注了四个方面 即:优雅编码设计模式.通过并发和编译后的Python(Cython)使处理速度更快.高层联网和图像.书中展示了在Python中已经过验证有用的设计模式,用专家级 ...

  3. 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 导读

    前 言 本书面向有志于拓展及深化Python知识的读者,它将教你如何改进Python程序的质量.可靠性.速度.可维护性以及可用性.书中包含大量实用的范例与思路,可帮助大家提升Python编程水平. 本 ...

  4. java多线程编程_Java多线程编程实战指南+设计模式篇.pdf

    Java多线程编程实战指南+设计模式篇.pdf 对Java架构技术感兴趣的工程师朋友们可以关注我,转发此文后私信我"Java"获取更多Java编程PDF资料(附送视频精讲) 关注我 ...

  5. python 创建线程打印_如何使用免费软件创建高质量的照片打印

    python 创建线程打印 Photoshop may be the professional standard for high quality photo prints, but that doe ...

  6. python编程实例-python编程实战

    广告关闭 2017年12月,云+社区对外发布,从最开始的技术博客到现在拥有多个社区产品.未来,我们一起乘风破浪,创造无限可能. 另外又学了几个python3的新decorator. 创建型设计模式抽象 ...

  7. Java多线程编程实战指南+设计模式篇pdf

    下载地址:网盘下载 随着CPU 多核时代的到来,多线程编程在充分利用计算资源.提高软件服务质量方面扮演了越来越重要的角色.而 解决多线程编程中频繁出现的普遍问题可以借鉴设计模式所提供的现成解决方案.然 ...

  8. Python编程风格和设计模式

    http://blog.csdn.net/pipisorry/article/details/26840461 the Zen of Python Python的禅学 >>> imp ...

  9. python图像处理实战 戴伊_这一套封面的程序员专业书籍你读过哪一本?

    以往我们总盯着畅销书,经典书,新书,今天给大家介绍Packt Publishing的程序员专业书籍.这一套封面的程序员书你读过哪一本? 1.Python图像处理实战 [印度] 桑迪潘·戴伊(Sandi ...

  10. Python编程快速上手让繁琐工作自动化中文高清完整版PDF带书签

    简介 如今,人们面临的大多数任务都可以通过编写计算机软件来完成.Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.通过Python编程,我们能够解决现实生活中的很多任务. 本书是一本 ...

最新文章

  1. 如何在Kubernetes集群动态使用 NAS 持久卷
  2. oracle 里面定时执行任务,比如存储过程内容等
  3. qt char*转int_Qt在Linux下绘制文字原理-使用FreeType来绘制字形
  4. 用TLS实现安全TCP传输及配置和访问https的web服务(转)
  5. 曲奇问答CEO:从产品经理的角度玩转社区类产品
  6. nutz ajax ie8,版本升级
  7. boost::fusion::single_view用法的测试程序
  8. Qt Creator使用补充工具栏
  9. maven添加sqlserver的jdbc驱动包
  10. Android Studio第二十七期 - RecycleView不同item布局
  11. sql 数据表归档_如何在考虑规模的情况下归档SQL Server数据
  12. 【论文写作】客户端设计与实现中模块程序实现如何写
  13. consul配置mysql集群_consul1.6实现Mysql-Gtid主从读写分离和高可用-03
  14. Java基础:如何读取控制台输入?如何读取字符?
  15. wx:if 与hidden
  16. 一个6年java程序员的工作感悟,写给还在迷茫的你
  17. 积累的历年博客终于发完了
  18. 「需求广场」需求词更新明细(六)
  19. linux访问mdio接口函数,Linux 下smi/mdio总线通信
  20. python+qqbot实现qq聊天机器人

热门文章

  1. ENVI入门系列教程---一、数据预处理---2.1自定义坐标系
  2. R^2 score is not well-defined with less than two samples
  3. IDL实现高分一号数据辐射定标TASK(二)
  4. Linkage 使用方法总结
  5. android实现Materia Design风格APP(四):ViewPager同时显示3个item及设置切换动画
  6. ios 添加浮动效果_iOS 为UITableView的索引 添加浮动放大View显示
  7. 两用物项许可证办理流程_[进出口管制类]两用物项和技术进口许可证之易制毒化学品(6)欣海丁丁检务工作室(272)...
  8. 为什么说 80% 的程序员都缺乏基本功?
  9. FFmpeg总结(十四)FFmpeg如何解析直播点播m3u8
  10. Android插件实战总结之TwsPluginFramework