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

简介:

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

2.5 外观模式

如果某套接口因为太过复杂或太专注于底层细节而变得不易使用,那么可考虑用“外观模式”(Fa?ade Pattern)将其简化并统合起来。
由Python标准库所提供的模块可以处理gzip、tarball、zip等格式的压缩文档,不过处理每种格式所用的接口却不相同。现在假定我们想通过一套简单而一致的接口来获知压缩文档里的各个文件名,并将其解压缩。本节将使用外观模式来设计这套接口,把真正的处理工作交给标准库来做。

63b9aef33973767ce13d95ae6ed2583dedf04bec

图2.7演示了我们想要提供给用户的接口(其中含有filename属性、names()方法与unpack()方法)以及该接口下掩藏的三套底层接口。Archive实例中存有压缩文档的名称,只有当用户询问其中压缩的文件名或要求对其解压缩时,才需要真正把压缩文档打开。(本节中的范例代码选自Unpack.py文件。)
screenshot

self._names变量用来保存一个callable,这个callable可以返回压缩文档内的文件名列表。self._unpack变量与之相似,其所存放的callable对象可把压缩文档中的所有文件都解压到当前目录。self._file存放打开的文件对象,此对象用来表示当前这份压缩文档。self.filename是个只读属性,用来保存压缩文档本身的文件名。
screenshot

如果用户打开了压缩文档之后又想修改filename属性(比如通过archive.filename = newname语句来修改),那么Archive会先把当前这份压缩文档关掉。由于Archive类采用了“延迟求值”(lazy evaluation)机制,所以修改属性之后,新的压缩文档并不会立即开启,只在有需要时才会打开它。
screenshot

按理说,用户在用完Archive类的实例之后,应该调用其close()方法。该方法会把已经打开的文件对象关掉,并把self._name、self._unpack和self._file设为None,令这些变量失效。
但实际上,用户只要在with语句范围内使用,就无须自行调用close(),因为我们已经把Archive类做成了“情境管理器”(context manager)。比方说,可以这样来使用它:
screenshot

上述代码创建了Archive实例,用来把zip格式压缩文档中的文件名打印到控制台上,并把其中所有文件都解压至当前目录。由于archive是个环境管理器,所以当程序执行到with语句块的范围之外时,会自动执行archive.close()。
screenshot

只要有上面这两个方法,Archive就能成为环境管理器。__enter__()方法返回self(也就是当前这个Archive实例),此返回值会赋给with ... as语句中的变量。__exit__()方法会把当前已经打开的文件对象关掉,由于此方法默认返回None,所以执行过程中发生的异常会照常传播。
screenshot

上述方法会返回压缩文档中的文件名列表,若该文档尚未开启,则调用self._prepare()方法将压缩文件打开,并把适当的callable赋给self._names与self._unpack。
screenshot

上述方法会把压缩文档中的所有文件都解压,不过我们稍后就会看到,只有在每个文件的名称都“安全”(safe)时,才会这么做。
screenshot

上述方法会把解压前的准备工作指派给合适的方法。由于tarball与zip格式所需的代码非常相似,所以我们把这两种压缩文档交由同一个方法处理。gzip格式的压缩文档与二者不同,因此单独放在另一个方法中处理。
这两个“准备方法”(preparation method)都必须把对应的callable赋给self.names及self._unpack变量,使刚才的names()与unpack()方法可以调用这些callable。
screenshot

上述方法首先创建名为safe_extractall()的嵌套函数,该函数检查压缩文档中的文件名是否均安全,如果is_safe()方法判定某些文件名不安全,那么safe_extractall()方法就会抛出ValueError。若是所有文件名都没问题,那么就调用tarball.TarFile.extractall()或zipfile.ZipFile.extractall()方法。
创建好safe_extractall()函数后,我们根据压缩文档的扩展名来创建tarfile.Tarfile或zipfile.ZipFile,并将其赋给self._file。接下来,把self._names的值设置成相应的绑定方法(也就是namelist()或getnames()),并把self._unpack的值设为刚刚创建好的safe_extractall()函数。由于该函数是闭包,所以能够捕获self,继而可通过self来访问self._file的值,并调用相应的extractall()方法。(绑定方法与非绑定方法的区别请参阅本节中的补充知识。)

            绑定方法与非绑定方法

绑定方法(bound method)就是已经同类实例相关联的方法。假设现在有Form类,类中有个update_ui()方法。如果在Form的某个方法里写上bound = self.update_ui这行代码,那就等于把指向Form.update_ui()方法的对象引用赋给了bound,并把Form.update_ui()方法同Form类的特定实例(用self表示)相绑定。绑定方法可以直接调用,比如:bound()。
非绑定方法(unbound method)是不与实例相关联的方法。比方说,假如刚才那行代码写的是unbound = Form.update_ui,那么unbound还是会成为指向Form.update_ui()方法的对象引用,但这次不会与特定的实例相绑定。也就是说,如果想调用非绑定方法,那么必须把适当的实例用作其首个参数才行,例如:form = Form(); unbound(form)。(与传统的Python不同,Python 3严格来说没有“非绑定方法”这一概念,所以bound就是个底层函数对象,这两种对非绑定方法的处理方式只有在元编程时才会偶尔体现出差别。)
screenshot

如果将恶意压缩文档解压缩,那么可能会把重要的系统文件覆写成无用或危险的内容。因此,切勿把包含绝对路径或相对路径的压缩文档打开,而且总应该以“非特权用户”(unprivileged user)的身份开启压缩文档(也就是不要以“根用户”(root)或“管理员”(Administrator)身份开启)。
如果文件名以“斜线”(forward slash)或“反斜线”(backslash)开头(这表示绝对路径),包含“../”、“..”(由于相对路径的目标不定,所以含有这两种文件名的压缩文档也不安全)或以“D:”这样的Windows盘符开头,那么is_safe()方法就返回False。
换句话说,以绝对路径开头或其中包含相对路径的文件名都是不安全的,而其他文件名则会使该方法返回True。
screenshot

上述方法将打开的文件对象赋给self._file变量,并把适当的callable赋给self._names及self._unpack变量。这次我们需要自己编写extractall()函数来读写相关数据。
外观模式很适合用来创建简单易用的接口,其优点在于能够隔离底层细节,而缺点则是可能会丧失某些微调能力。然而外观模式并不会把底层功能遮掩或废弃,所以大部分时间里都可以直接使用外观模式,而在需要微调时,则可以深入底层的类。
外观模式看起来很像适配器模式,其区别在于,外观模式是在复杂的接口上提炼出一套简单的接口,而适配器则是把其他接口(未必很复杂)转换成标准接口。这两种模式可以结合起来。比方说,我们可以定义一套处理压缩文档的标准接口(能够处理tarball、zip及Windows系统的.cab等格式),并用适配器模式把每种格式的压缩文档处理接口都分别转换成标准接口,然后在标准接口上面搭建外观层,这样用户就无须关注当前操作的压缩文档是哪种格式了。

相关文章
|
28天前
|
设计模式 存储 Java
【九】设计模式~~~结构型模式~~~外观模式(Java)
文章详细介绍了外观模式(Facade Pattern),这是一种对象结构型模式,通过引入一个外观类来简化客户端与多个子系统之间的交互,降低系统的耦合度,并提供一个统一的高层接口来使用子系统。通过文件加密模块的实例,展示了外观模式的动机、定义、结构、优点、缺点以及适用场景,并讨论了如何通过引入抽象外观类来提高系统的可扩展性。
【九】设计模式~~~结构型模式~~~外观模式(Java)
|
19天前
|
设计模式 XML 数据格式
python之工厂设计模式
python之工厂设计模式
python之工厂设计模式
|
2月前
|
并行计算 Python
python 并发与并行
【7月更文挑战第21天】
33 5
python 并发与并行
|
2月前
|
安全 数据安全/隐私保护 Python
|
2月前
|
并行计算 监控 数据处理
构建高效Python应用:并发与异步编程的实战秘籍,IO与CPU密集型任务一网打尽!
【7月更文挑战第16天】Python并发异步提升性能:使用`asyncio`处理IO密集型任务,如网络请求,借助事件循环实现非阻塞;`multiprocessing`模块用于CPU密集型任务,绕过GIL进行并行计算。通过任务类型识别、任务分割、避免共享状态、利用现代库和性能调优,实现高效编程。示例代码展示异步HTTP请求和多进程数据处理。
44 8
|
1月前
|
设计模式 存储 数据库连接
Python设计模式:巧用元类创建单例模式!
Python设计模式:巧用元类创建单例模式!
30 0
|
2月前
|
算法 Java 程序员
解锁Python高效之道:并发与异步在IO与CPU密集型任务中的精准打击策略!
【7月更文挑战第17天】在数据驱动时代,Python凭借其优雅语法和强大库支持成为并发处理大规模数据的首选。并发与异步编程是关键,包括多线程、多进程和异步IO。对于IO密集型任务,如网络请求,可使用`concurrent.futures`和`asyncio`;CPU密集型任务则推荐多进程,如`multiprocessing`;`asyncio`适用于混合任务,实现等待IO时执行CPU任务。通过这些工具,开发者能有效优化资源,提升系统性能。
69 4
|
2月前
|
分布式计算 并行计算 Java
Python并发风暴来袭!IO密集型与CPU密集型任务并发策略大比拼,你站哪队?
【7月更文挑战第17天】Python并发处理IO密集型(如网络请求)与CPU密集型(如数学计算)任务。IO密集型适合多线程和异步IO,如`ThreadPoolExecutor`进行网页下载;CPU密集型推荐多进程,如`multiprocessing`模块进行并行计算。选择取决于任务类型,理解任务特性是关键,以实现最佳效率。
43 4
|
2月前
|
开发框架 并行计算 .NET
脑洞大开!Python并发与异步编程的哲学思考:IO密集型与CPU密集型任务的智慧选择!
【7月更文挑战第18天】在Python中,异步编程(如`asyncio`)适合处理IO密集型任务,通过非阻塞操作提高响应性,例如使用`aiohttp`进行异步HTTP请求。而对于CPU密集型任务,由于GIL的存在,多进程(`multiprocessing`)能实现并行计算,如使用进程池进行大量计算。明智选择并发模型是性能优化的关键,体现了对任务特性和编程哲学的深刻理解。
28 2
|
2月前
|
开发框架 并行计算 算法
揭秘Python并发神器:IO密集型与CPU密集型任务的异步革命,你竟还傻傻分不清?
【7月更文挑战第18天】Python并发编程中,异步IO适合IO密集型任务,如异步HTTP请求,利用`asyncio`和`aiohttp`实现并发抓取,避免等待延迟。而对于CPU密集型任务,如并行计算斐波那契数列,多进程通过`multiprocessing`库能绕过GIL限制实现并行计算。选择正确的并发模型能显著提升性能。
64 2