《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
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
44 3
|
2月前
|
Python
解锁Python并发新世界:线程与进程的并行艺术,让你的应用性能翻倍!
【7月更文挑战第9天】并发编程**是同时执行多个任务的技术,提升程序效率。Python的**threading**模块支持多线程,适合IO密集型任务,但受GIL限制。**multiprocessing**模块允许多进程并行,绕过GIL,适用于CPU密集型任务。例如,计算平方和,多线程版本使用`threading`分割工作并同步结果;多进程版本利用`multiprocessing.Pool`分块计算再合并。正确选择能优化应用性能。
27 1
|
17天前
|
设计模式 XML 数据格式
python之工厂设计模式
python之工厂设计模式
python之工厂设计模式
|
26天前
|
设计模式 XML 存储
【六】设计模式~~~结构型模式~~~适配器模式(Java)
文章详细介绍了适配器模式(Adapter Pattern),这是一种结构型设计模式,用于将一个类的接口转换成客户期望的另一个接口,使原本不兼容的接口能够一起工作,提高了类的复用性和系统的灵活性。通过对象适配器和类适配器两种实现方式,展示了适配器模式的代码应用,并讨论了其优点、缺点以及适用场景。
|
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设计模式:巧用元类创建单例模式!
29 0
|
2月前
|
算法 Java 程序员
解锁Python高效之道:并发与异步在IO与CPU密集型任务中的精准打击策略!
【7月更文挑战第17天】在数据驱动时代,Python凭借其优雅语法和强大库支持成为并发处理大规模数据的首选。并发与异步编程是关键,包括多线程、多进程和异步IO。对于IO密集型任务,如网络请求,可使用`concurrent.futures`和`asyncio`;CPU密集型任务则推荐多进程,如`multiprocessing`;`asyncio`适用于混合任务,实现等待IO时执行CPU任务。通过这些工具,开发者能有效优化资源,提升系统性能。
68 4
|
2月前
|
分布式计算 并行计算 Java
Python并发风暴来袭!IO密集型与CPU密集型任务并发策略大比拼,你站哪队?
【7月更文挑战第17天】Python并发处理IO密集型(如网络请求)与CPU密集型(如数学计算)任务。IO密集型适合多线程和异步IO,如`ThreadPoolExecutor`进行网页下载;CPU密集型推荐多进程,如`multiprocessing`模块进行并行计算。选择取决于任务类型,理解任务特性是关键,以实现最佳效率。
43 4