《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天前
|
设计模式 缓存 安全
设计模式——代理模式
静态代理、JDK动态代理、Cglib 代理
设计模式——代理模式
|
11天前
|
存储 Shell 区块链
怎么把Python脚本打包成可执行程序?
该文档介绍了如何将Python脚本及其运行环境打包成EXE可执行文件,以便在不具备Python环境的计算机上运行。首先确保Python脚本能够正常运行,然后通过安装PyInstaller并使用`--onefile`参数将脚本打包成独立的EXE文件。此外,还提供了去除命令行窗口和指定可执行文件图标的详细方法。这些步骤帮助用户轻松地将Python程序分发给最终用户。
怎么把Python脚本打包成可执行程序?
|
6天前
|
Python
探索Python编程的奥秘:打造你的第一个程序
【9月更文挑战第8天】本文将带你进入Python编程的世界,通过一个有趣的项目——制作一个简单的猜数字游戏,让你快速入门。我们不仅会分享代码编写的步骤,还会讲解每一行代码的含义和作用,确保即使是编程新手也能跟上节奏。文章末尾附有完整代码,方便读者实践和学习。
21 12
|
3天前
|
JSON 监控 数据挖掘
Python I/O管理新篇章:优化你的程序,让数据流动更顺畅
【9月更文挑战第13天】在数据驱动时代,Python因其在数据分析、科学计算及Web开发中的广泛应用而备受青睐。本文探讨了Python I/O管理的关键方面,包括理解I/O瓶颈、使用缓冲技术、并发与异步I/O、高效数据序列化及监控调试。通过具体示例,展示了如何优化文件读写和网络通信,提高程序性能。掌握这些技能,可使数据在程序中流动更加顺畅。
12 3
|
3天前
|
Python
惊!Python进程间通信IPC,让你的程序秒变社交达人,信息畅通无阻
【9月更文挑战第13天】在编程的世界中,进程间通信(IPC)如同一场精彩的社交舞会,每个进程通过优雅的IPC机制交换信息,协同工作。本文将带你探索Python中的IPC奥秘,了解它是如何让程序实现无缝信息交流的。IPC如同隐形桥梁,连接各进程,使其跨越边界自由沟通。Python提供了多种IPC机制,如管道、队列、共享内存及套接字,适用于不同场景。通过一个简单的队列示例,我们将展示如何使用`multiprocessing.Queue`实现进程间通信,使程序如同社交达人般高效互动。掌握IPC,让你的程序在编程舞台上大放异彩。
10 3
|
10天前
|
算法 程序员 Linux
Python编程入门:构建你的第一个程序
【9月更文挑战第4天】编程是现代技术发展的基石,而Python作为一门简洁、易学且功能强大的编程语言,已成为众多初学者的首选。本文将引导你通过一个简单的Python程序,探索编程世界的奥秘,并了解如何利用Python实现基本的算法逻辑。无论你是完全的新手还是希望巩固基础的开发者,这篇文章都将为你提供一个清晰的学习路径。从安装Python环境开始,到编写第一个程序,我们将一步步揭开编程的神秘面纱。
|
16天前
|
Python
Python编程入门:构建你的第一个程序
【8月更文挑战第31天】本文是面向初学者的Python编程指南,将引导读者通过实际案例学习Python基础。我们将一起编写一个简单的程序来展示如何运用变量、控制结构和函数等概念。文章末尾附有完整代码示例供参考和实践。
|
15天前
|
小程序 Python
Python 编程入门:打造你的第一个程序
【8月更文挑战第31天】 在数字化时代,编程已成为一项宝贵的技能。本文将通过一个简单示例引导初学者步入Python编程的世界。我们将从基础语法开始,逐步构建一个小程序,并在此过程中探索编程的逻辑思维与问题解决策略。无论你是科技爱好者还是职场新人,这篇文章都将为你开启编程之旅提供助力。
|
15天前
|
程序员 Python
Python 编程入门:打造你的第一个程序
【8月更文挑战第31天】编程初学者常常在起步时感到迷茫。本文将通过浅显易懂的方式,带领读者从零开始,一步步构建他们的第一个 Python 程序。我们将探索 Python 的安装、基础语法,并通过一个实际的项目——简易计算器,来巩固学习成果。无论你的背景如何,这篇文章都将为你打开编程世界的大门,让你轻松上手,享受编程的乐趣。
|
15天前
|
IDE 程序员 开发工具
Python编程入门:构建你的第一个程序
【8月更文挑战第31天】 本文是专为编程新手设计的Python入门指南,旨在通过一个简单实例引导读者了解Python编程的基础。文章将逐步展示如何从零开始编写一个“Hello, World!”程序,并解释代码中每一部分的功能,帮助初学者建立对Python语言的初步认识和理解。通过本文,读者将学会设置编程环境、编写简单的Python代码以及运行和调试程序。