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

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

2.5 外观模式

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

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

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

self._names变量用来保存一个callable,这个callable可以返回压缩文档内的文件名列表。self._unpack变量与之相似,其所存放的callable对象可把压缩文档中的所有文件都解压到当前目录。self._file存放打开的文件对象,此对象用来表示当前这份压缩文档。self.filename是个只读属性,用来保存压缩文档本身的文件名。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.5 外观模式..._第2张图片

如果用户打开了压缩文档之后又想修改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)时,才会这么做。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.5 外观模式..._第3张图片

上述方法会把解压前的准备工作指派给合适的方法。由于tarball与zip格式所需的代码非常相似,所以我们把这两种压缩文档交由同一个方法处理。gzip格式的压缩文档与二者不同,因此单独放在另一个方法中处理。
这两个“准备方法”(preparation method)都必须把对应的callable赋给self.names及self._unpack变量,使刚才的names()与unpack()方法可以调用这些callable。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.5 外观模式..._第4张图片

上述方法首先创建名为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。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.5 外观模式..._第5张图片

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

你可能感兴趣的:(《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.5 外观模式...)