《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等格式),并用适配器模式把每种格式的压缩文档处理接口都分别转换成标准接口,然后在标准接口上面搭建外观层,这样用户就无须关注当前操作的压缩文档是哪种格式了。

相关文章
|
13天前
|
设计模式 Java
23种设计模式,外观模式的概念优缺点以及JAVA代码举例
【4月更文挑战第6天】外观模式(Facade Pattern)是一种使用频率非常高的结构型设计模式,其核心思想是为子系统中的一组接口提供一个一致的界面。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。简而言之,外观模式就是客户端与复杂子系统之间的一个简单而统一的接口
38 3
|
13天前
|
数据采集 Java Python
python并发编程:Python异步IO实现并发爬虫
python并发编程:Python异步IO实现并发爬虫
24 1
|
13天前
|
设计模式 API 数据安全/隐私保护
探索设计模式的魅力:外观模式简化术-隐藏复杂性,提供简洁接口的设计秘密
外观模式是一种关键的设计模式,旨在通过提供一个简洁的接口来简化复杂子系统的访问。其核心价值在于将复杂的内部实现细节封装起来,仅通过一个统一的外观对象与客户端交互,从而降低了系统的使用难度和耦合度。在软件开发中,外观模式的重要性不言而喻。它不仅能够提高代码的可读性、可维护性和可扩展性,还能促进团队间的协作和沟通。此外,随着业务需求和技术的发展,外观模式能够适应变化,通过修改外观对象来灵活调整客户端与子系统之间的交互方式。总之,外观模式在软件设计中扮演着举足轻重的角色,是构建高效、稳定且易于维护的软件系统的关键
80 1
探索设计模式的魅力:外观模式简化术-隐藏复杂性,提供简洁接口的设计秘密
|
13天前
|
设计模式 存储 uml
C++ 设计模式实战:外观模式和访问者模式的结合使用,派生类访问基类的私有子系统
C++ 设计模式实战:外观模式和访问者模式的结合使用,派生类访问基类的私有子系统
36 1
|
4天前
|
机器学习/深度学习 网络协议 数据库
Python编程实战:解决常见编程问题
```markdown Python编程入门指南:涵盖文件操作、列表操作、字符串处理、函数编写、异常处理、网络编程和数据库操作等实战案例。通过示例代码,学习如何读写文件、排序列表、转换字符串、创建函数、处理异常、构建TCP服务器及操作SQLite数据库,逐步掌握Python核心技能。 ```
|
10天前
|
网络协议 Python
Python 网络编程实战:构建高效的网络应用
【5月更文挑战第18天】Python在数字化时代成为构建网络应用的热门语言,因其简洁的语法和强大功能。本文介绍了网络编程基础知识,包括TCP和UDP套接字,强调异步编程、数据压缩和连接池的关键作用。提供了一个简单的TCP服务器和客户端代码示例,并提及优化与改进方向,鼓励读者通过实践提升网络应用性能。
28 6
|
10天前
|
Python
深入 Python 套接字编程:高级特性与并发处理
【5月更文挑战第18天】本文探讨了Python套接字编程的高级特性,包括非阻塞套接字、超时设置和端口复用,以及并发处理方法:多线程、多进程和异步I/O(如`asyncio`)。通过示例展示了多线程服务器如何处理客户端连接。同时强调并发处理时需注意资源竞争和同步,并合理配置线程数。学习这些技能将有助于构建高效、灵活的网络应用,应对不断发展的技术挑战。
28 10
|
11天前
|
Java 测试技术 Python
Python的多线程允许在同一进程中并发执行任务
【5月更文挑战第17天】Python的多线程允许在同一进程中并发执行任务。示例1展示了创建5个线程打印"Hello World",每个线程调用同一函数并使用`join()`等待所有线程完成。示例2使用`ThreadPoolExecutor`下载网页,创建线程池处理多个URL,打印出每个网页的大小。Python多线程还可用于线程间通信和同步,如使用Queue和Lock。
34 1
|
13天前
|
设计模式 API
【设计模式】什么是外观模式并给出例子!
【设计模式】什么是外观模式并给出例子!
15 0
|
13天前
|
设计模式 JavaScript 前端开发
[设计模式Java实现附plantuml源码~结构型] 提供统一入口——外观模式
[设计模式Java实现附plantuml源码~结构型] 提供统一入口——外观模式