《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上面了。

相关文章
|
2天前
|
Python Windows
Python基础教程(第3版)中文版 第18章 程序打包 (笔记)
Python基础教程(第3版)中文版 第18章 程序打包 (笔记)
|
1天前
|
Python 索引
【Python字符串攻略】:玩转文字,编织程序的叙事艺术
【Python字符串攻略】:玩转文字,编织程序的叙事艺术
|
1天前
|
Python 存储 数据处理
【Python数据类型的奥秘】:构建程序基石,驾驭信息之海
【Python数据类型的奥秘】:构建程序基石,驾驭信息之海
|
1天前
|
XML 数据格式 Python
Python零基础入门-1 从一行代码开始运行Python程序(续)
Python零基础入门-1 从一行代码开始运行Python程序(续)
|
1天前
|
测试技术 Python
Python零基础入门-1 从一行代码开始运行Python程序
Python零基础入门-1 从一行代码开始运行Python程序
|
2天前
|
存储 区块链 Python
怎么把Python脚本打包成可执行程序?
【6月更文挑战第3天】最近根据用户提的需求用python做了一个小工具,但是在给客户使用的时候不能直接发送python文件,毕竟让客户去安装python环境,那就离了大谱了。所以这时候就需要把多个py文件带着运行环境打包成EXE可执行文件。
8 1
|
2天前
|
搜索推荐 区块链 开发者
【python程序打包教程】PyInstaller一键打包Python程序为独立可执行exe文件
【python程序打包教程】PyInstaller一键打包Python程序为独立可执行exe文件
|
4天前
|
Python
Python编程实战:如何将列表组装成一棵树结构
本文介绍了如何在Python中将列表转换为树结构。首先定义`TreeNode`类表示节点,包含值和子节点列表。然后,通过`list_to_tree`函数递归地将列表转为树。此外,还提供了添加和删除节点的方法。文章旨在帮助读者理解和操作树结构,以解决实际编程问题。
Python编程实战:如何将列表组装成一棵树结构
|
6天前
|
调度 数据库 开发者
在Python编程中,并发编程和异步IO是两个重要的概念,它们对于提高程序性能和响应速度具有至关重要的作用
【6月更文挑战第10天】本文介绍了Python并发编程和异步IO,包括并发编程的基本概念如多线程、多进程和协程。线程和进程可通过threading及multiprocessing模块管理,但多线程受限于GIL。协程利用asyncio模块实现非阻塞IO,适合处理IO密集型任务。异步IO基于事件循环,能提高服务器并发处理能力,适用于网络编程和文件操作等场景。异步IO与多线程、多进程在不同任务中有各自优势,开发者应根据需求选择合适的技术。
18 0
|
7天前
|
设计模式 Java C#
【设计模式】第六篇:来康康适配器模式
类适配器模式 主要使用继承实现,耦合度高,且在单继承的语言中使用会受限,还需要防止继承带来的一些问题
13 5