《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.1 适配器模式

简介:

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

2.1 适配器模式

“适配器模式”(Adapter Pattern)是一种接口适配技术,可通过某个类来使用另一个接口与之不兼容的类,运用此模式时,两个类的接口都无须改动。这项技术非常有用,比方说,我们想把某个类从其原先的应用场景中拿出来放在另一个环境下运行,而这个类又不能修改,那就可以考虑适配器模式。
假设有个简单的Page类用于渲染页面,它需要知道标题、正文段落以及“渲染器类”(renderer class)的实例。(本节代码均选自render1.py范例程序。)
screenshot
screenshot

Page类并不知道也无须关心传进来的渲染器类实例具体是什么,它只要知道渲染器提供了渲染页面所需的接口就好,也就是说,渲染器类应该有三个方法:header(str)、paragraph(str)、footer()。
在本例中,我们需要保证__init__()接收到的renderer参数确实是个Renderer实例。有一种简单但是很糟糕的办法,就是用assert isinstance(renderer, Renderer)语句来判断。这么做有两个缺陷。首先,它抛出的是AssertionError,而不是我们所期望的TypeError,后者更为具体。其次,假如运行程序时指定了-o选项(“optimize”,优化),那么assert语句就不会执行,而稍后执行render()方法时,将会导致AttributeError异常。范例代码中的if not isinstance(...)语句则没有这两个问题,它可以抛出TypeError异常,而且在加了-o选项后依然能正确运行。
但这种写法也有个明显的问题,那就是所有渲染器子类似乎都必须继承自Renderer基类。假如用C++语言来编程,那确实如此,而在Python语言里也是可以创建这种基类的。不过,Python的abc(abstract base class,抽象基类)模块提供了另一种做法,既能像抽象基类那样检查接口是否匹配,又能像“动态类型”(duck typing)那样非常灵活。这就是说,我们可以在无须继承特定基类的前提下,创建出符合某套接口(也就是具备特定API)的对象来。
screenshot

Renderer类重新实现了__subclasshook__()这个“特殊方法”(special method)。Python语言内置的isinstance()函数要通过此方法来决定函数的首个参数是不是第二个参数的子类(如果第二个参数是由类所构成的元组,那就判断首个参数是不是元组中某个类的子类)。
上面这段代码有些棘手,它必须在Python 3.3及之后的版本上才能运行,因为其中用到了collections.ChainMap类。这段代码的原理稍后解释,但就算不明白也无关紧要,因为这些复杂的操作都可以通过范例代码中的@Qtrac.has_methods“类装饰器”(class decorator)来完成,2.2节将会演示其用法。
__subclasshook__()特殊方法首先通过Class参数判断该自己是不是在Renderer类上面调用的,如果不是,就返回NotImplemented。这么做意味着子类无法继承__subclasshook__()的行为。由于我们假定子类要在抽象基类的基础上添加新的标准而不是新的行为,所以才设计成这样。若想继承__subclasshook__()的行为也可以,只要在重新实现__subclasshook__()的时候调用Renderer.__subclasshook__()就可以了。
此方法如果返回True或False,那么isinstance()的判定流程就会在这个抽象基类处终止,并返回bool值。若返回NotImplemented,则会沿着继承体系按照通常的规则继续判定下去(判断Subclass是不是本类的子类、是不是“显式注册类”(explicitly registered class)的子类、是不是子类的子类)。
如果满足了if语句的判断条件,那就调用Subclass的__mro__()特殊方法,并遍历Subclass及所有超类(包括Subclass本身)的私有字典(也就是__dict__)。遍历好的字典会放在元组中,我们通过序列解包操作(*)将其传给collections.ChainMap()函数。此函数会创建一份Map视图,把它从参数中收到的所有映射表(比如字典就可以当作映射表传进去)都当成一张映射表看待。接下来,将待检测的方法放在另一个元组中。最后,遍历元组中的方法,判断它们是不是都在attributes映射表中,这张映射表的键是Subclass及其全部超类的所有方法名与属性名。如果methods中的每个方法都在attributes映射表里,那就返回True。
请注意,上面这段代码只检测了Subclass及其全部基类的所有attribute名称是不是涵盖了我们所需的那些方法,并没有详细判断attribute到底是属性还是方法。如果某属性恰好与所需方法同名,那它也能通过检测。假如检测时想排除属性名而只考虑方法名,那么可在method in attributes这行判断语句中加上and callable(method)。由于此问题在实际编程中很少遇到,所以没必要专门改写。
用__subclasshook__()来创建带有接口检查功能的类是一项非常有用的技术,但如果每个类都要写这十几行代码的话,那就显得重复了,因为这些类之间的差别可能不大,只是基类与所支持的方法不同而已。在下一节中,我们将通过类装饰器来避免重复代码,也就是说,有了类装饰器之后,每次只需编写一两行特殊代码,就能创建出具备接口检查功能的类。(下一节的render2.py范例程序演示了这种装饰器的用法。)
screenshot

上面这个简单的类可以当成页面渲染器来用,因为它具备相关接口。
header()方法会根据指定的宽度把标题输出到正中位置,然后换一行,在标题的每个字母下面输出“=”字符。
screenshot

paragraph()方法使用Python标准库的textwrap模块把文本段按照指定的宽度换行,并打印出来。使用self.previous这个Boolean变量是为了保证除第一段以外,后面每两段之间都有空行隔开。由于页面渲染器的接口定义了footer(),所以即便不打印页脚,也得写个什么事都不做的方法放在这里。
screenshot

HtmlWriter类可用来写出简单的HTML页面,它用html.escape()函数处理转义字符(在Python 3.2及早前版本中,使用xml.sax.saxutil.escape()函数)。
尽管这个类也有header()及footer()方法,但其行为却和页面渲染器接口所定义的不同。所以,在构建Page实例时,我们可以传入TextRenderer对象,但却不能直接把HtmlWriter对象当成页面渲染器传进去。
一种解决办法是编写HtmlWriter的子类,并在子类中提供页面渲染器所需的接口方法。但这种方案很容易出错,因为子类会把HtmlWriter的方法同页面渲染器的接口方法混在一起。还有个更好的办法就是创建适配器,令其把我们要使用的HtmlWriter类“聚合”(aggregate)进来,并提供Renderer所定义的接口,然后将聚合进来的类与接口适配好。图2.1演示了如何引入适配器类。
screenshot

上面列出的就是适配器类。在构造时,可把HtmlWriter对象当成htmlWriter参数传入,该类负责提供页面渲染器接口所需的方法。由于实际渲染任务都会委托给聚合进来的HtmlWriter对象,所以HtmlRenderer类只相当于在现有的HtmlWriter类的外围包了一层新的接口而已。
screenshot
screenshot

上面几行代码演示了如何用两种渲染器来创建Page类的实例。在构建TextRenderer时,我们将默认宽度设为22个字符。而在用HtmlRenderer来创建HtmlWriter适配器时,我们则把一份打开的文件传了进去(创建此文件所用的语句没有列出来),这样的话,HTML就不会渲染到默认的sys.stdout上面了。

相关文章
|
3天前
|
Linux Python Windows
打包Python程序文件:pyinstaller实现
本文介绍基于Python语言中的pyinstaller模块,将写好的.py格式的Python代码及其所用到的所有第三方库打包,生成.exe格式的可执行文件,从而方便地在其他环境、其他电脑中直接执行这一可执行文件的方法。
|
4天前
|
数据采集 JSON 数据库
800个程序实例、5万行代码!清华大学出版【Python王者归来】
Python 的丰富模块(module)以及广泛的应用范围,使 Python 成为当下最重要的计算机语言之一,本书尝试将所有常用模块与应用分门别类组织起来,相信只要读者遵循本书实例,定可以轻松学会 Python 语法与应用,逐步向 Python 高手之路迈进,这也是撰写本书的目的。 本书以约 800 个程序实例讲解了:完整的 Python 语法,Python 的输入与输出,Python 的数据型态,列表(list)、元组(tuple)、字典(dict)、集合(set),函数设计,类别设计,使用系统与外部模块(module),设计自己的模块(module),文件压缩与解压缩,程序除错与异常处理…
|
7天前
|
数据库连接 Python
如何提高python程序代码的健壮性
在编程的时候,我们难免会遇到一些不可靠的情况,比如网络请求失败,数据库连接超时等等。这些不确定性会让我们的程序容易出现各种错误和异常。那么如何来增加程序的容错性和健壮性呢? 可能大多数人会想到使用try except来进行异常捕捉进行失败重试(Retry)。虽然try-escept一个非常常见和有效的方式来增强程序稳定性,但是可能一不小心就会造成栈溢出。 所以接下来我就来介绍一个另外的一个专门用于失败重试的库:retrying。
|
7天前
|
存储 算法 Python
Python编程作业一:程序基本流程
Python编程作业一:程序基本流程
14 0
|
7天前
|
网络协议 Python
在python中利用TCP协议编写简单网络通信程序,要求服务器端和客户端进行信息互传。 - 蓝易云
在这个示例中,服务器端创建一个socket并监听本地的12345端口。当客户端连接后,服务器发送一条欢迎消息,然后关闭连接。客户端创建一个socket,连接到服务器,接收消息,然后关闭连接。
62 0
|
8天前
|
数据采集 安全 数据挖掘
2024年最新7 年 Python 的我,总结了这 90 条写 Python 程序的建议,上海大厂Python面试经历
2024年最新7 年 Python 的我,总结了这 90 条写 Python 程序的建议,上海大厂Python面试经历
2024年最新7 年 Python 的我,总结了这 90 条写 Python 程序的建议,上海大厂Python面试经历
|
8天前
|
Python
2024年最新【Python】程序的组织结构:顺序结构,2024年最新46道面试题带你了解中高级Python面试
2024年最新【Python】程序的组织结构:顺序结构,2024年最新46道面试题带你了解中高级Python面试
2024年最新【Python】程序的组织结构:顺序结构,2024年最新46道面试题带你了解中高级Python面试
|
8天前
|
机器学习/深度学习 数据采集 数据挖掘
90%的人说Python程序慢,5大神招让你的代码像赛车一样跑起来_代码需要跑很久怎么办(2)
90%的人说Python程序慢,5大神招让你的代码像赛车一样跑起来_代码需要跑很久怎么办(2)
|
9天前
|
机器学习/深度学习 数据采集 Java
如何提高Python程序的性能
Python作为一种高级编程语言,具有易学易用、开发效率高等优点,但其在性能上可能不如C++或Java。本文将介绍如何通过一些技巧和工具来提高Python程序的性能。
|
10天前
|
Python
深入 Python 套接字编程:高级特性与并发处理
【5月更文挑战第18天】本文探讨了Python套接字编程的高级特性,包括非阻塞套接字、超时设置和端口复用,以及并发处理方法:多线程、多进程和异步I/O(如`asyncio`)。通过示例展示了多线程服务器如何处理客户端连接。同时强调并发处理时需注意资源竞争和同步,并合理配置线程数。学习这些技能将有助于构建高效、灵活的网络应用,应对不断发展的技术挑战。
28 10