《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编程入门——从零开始构建你的第一个程序
【10月更文挑战第39天】本文将带你走进Python的世界,通过简单易懂的语言和实际的代码示例,让你快速掌握Python的基础语法。无论你是编程新手还是想学习新语言的老手,这篇文章都能为你提供有价值的信息。我们将从变量、数据类型、控制结构等基本概念入手,逐步过渡到函数、模块等高级特性,最后通过一个综合示例来巩固所学知识。让我们一起开启Python编程之旅吧!
|
14天前
|
存储 NoSQL 数据库连接
在Python程序中实现LevelDB的海量key的分批次扫描
通过本文的步骤,您可以在Python程序中实现对LevelDB海量key的分批次扫描。这样不仅能够有效地管理大规模数据,还可以避免一次性加载过多数据到内存中,提高程序的性能和稳定性。希望这篇指南能为您的开发工作提供实用的帮助。
59 28
|
1月前
|
安全 API C语言
Python程序的安全逆向(关于我的OPENAI的APIkey是如何被盗的)
本文介绍了如何使用C语言编写一个简单的文件加解密程序,并讨论了如何为编译后的软件添加图标。此外,文章还探讨了Python的.pyc、.pyd等文件的原理,以及如何生成和使用.pyd文件来增强代码的安全性。通过视频和教程,作者详细讲解了生成.pyd文件的过程,并分享了逆向分析.pyd文件的方法。最后,文章提到可以通过定制Python解释器来进一步保护源代码。
75 6
|
1月前
|
设计模式 JSON 前端开发
前端必须掌握的设计模式——适配器模式
适配器模式是一种结构型设计模式,用于使接口不兼容的对象能够相互合作。通过在客户端和系统之间引入一个“中间层”适配器,将不同类型的输入数据转换为系统能处理的标准格式,减轻系统的负担,提高扩展性和可维护性。例如,MacBook的扩展坞将多种接口(如HDMI、USB)转换为Type-C接口,实现多接口兼容。
|
1月前
|
IDE 程序员 开发工具
Python编程入门:打造你的第一个程序
迈出编程的第一步,就像在未知的海洋中航行。本文是你启航的指南针,带你了解Python这门语言的魅力所在,并手把手教你构建第一个属于自己的程序。从安装环境到编写代码,我们将一步步走过这段旅程。准备好了吗?让我们开始吧!
|
27天前
|
Shell 开发工具 Python
如何在vim里直接运行python程序
如何在vim里直接运行python程序
|
2月前
|
开发者 Python
使用Python实现自动化邮件通知:当长时程序运行结束时
本文介绍了如何使用Python实现自动化邮件通知功能,当长时间运行的程序完成后自动发送邮件通知。主要内容包括:项目背景、设置SMTP服务、编写邮件发送函数、连接SMTP服务器、发送邮件及异常处理等步骤。通过这些步骤,可以有效提高工作效率,避免长时间等待程序结果。
88 9
|
2月前
|
存储 人工智能 数据挖掘
Python编程入门:打造你的第一个程序
本文旨在为初学者提供Python编程的初步指导,通过介绍Python语言的基础概念、开发环境的搭建以及一个简单的代码示例,帮助读者快速入门。文章将引导你理解编程思维,学会如何编写、运行和调试Python代码,从而开启编程之旅。
58 2
|
2月前
|
Python
在Python中,`try...except`语句用于捕获和处理程序运行时的异常
在Python中,`try...except`语句用于捕获和处理程序运行时的异常
70 5
|
2月前
|
存储 Python
Python编程入门:理解基础语法与编写简单程序
本文旨在为初学者提供一个关于如何开始使用Python编程语言的指南。我们将从安装Python环境开始,逐步介绍变量、数据类型、控制结构、函数和模块等基本概念。通过实例演示和练习,读者将学会如何编写简单的Python程序,并了解如何解决常见的编程问题。文章最后将提供一些资源,以供进一步学习和实践。
62 1

热门文章

最新文章