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

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

2.2 桥接模式

“桥接模式”(Bridge Pattern)用于将“抽象”(abstraction,比如接口或算法)与实现方式相分离。
如果不用桥接模式,那么通常的写法是,创建若干个基类,用于表示各种抽象方式,然后从每个基类中继承出两个或多个子类,用于表示对这种抽象方式的不同实现办法。用了桥接模式之后,我们需要创建两套独立的“类体系”(class hierarchy):“抽象体系”定义了我们所要执行的操作(比如接口或高层算法),而“实现体系”则包含具体实现方式,抽象体系要调用实现体系以完成其操作。抽象体系中的类会把实现体系中的某个类实例聚合进来,而这个实例将充当抽象接口与具体实现之间的桥梁(bridge)。
在前一节所讲的适配器模式中,HtmlRenderer类就可称为桥接模式,因为它为了完成渲染操作,把HtmlWriter实例聚合进来了。
本节要编写一个类,它可以用特定算法来绘制条形图,然而我们想把具体的算法实现代码放在其他类中。barchart1.py范例程序就是以这种方式来完成此功能的,它使用了桥接模式。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.2 桥接模式..._第1张图片

BarCharter类在其render()方法中描述了条形图绘制算法,而该算法要通过符合条形图渲染接口的渲染器来实现。这个渲染器需要具备接口中定义的四个方法:initialize(int, int)、draw_caption(str)、draw_bar(str, int)、finalize()。
与前一节类似,我们也通过isinstance()来判断传入的renderer对象是否具备所需的接口,这样就无须强迫条形图渲染器必须实现某个特定的基类了。但这次我们不像上一节那样用十行代码编写一个类,而是只用两行代码来创建专门用于检查接口的类。
screenshot

上面这段代码先创建了名为BarRenderer的类,并设置好使用abc模块所需的metaclass属性。然后,这个类会传给Qtrac.has_methods()函数,该函数将返回一个“类修饰器”。修饰器会向受修饰的类里添加__subclasshook__()类方法。当我们调用isinstance()来检测某个实例是不是BarRenderer类型时,这个新添加的方法会判断实例对应的类是否具备所需的方法。(如果不熟悉类修饰器,那么请先阅读2.4节,尤其是2.4.2节,然后再回到这里。)
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.2 桥接模式..._第2张图片

Qtrac.py模块的has_methods()函数通过methods参数来捕获用户所需的方法,然后创建类修饰器函数,并将其返回。修饰器函数本身又创建了__subclasshook__()函数,并用Python内置的classmethod()函数将其设置成基类的类方法。这个__subclasshook__()函数的代码与上一节所列的基本相同,区别在于这次没有把基类写成硬代码,而是通过Base参数来表示那个受修饰的类,另外,所需检测的方法名也没写成硬代码,而是用has_methods()函数的methods参数来表示的。
要实现接口检测功能,还有个办法,就是继承通用的抽象基类。例如:
screenshot

上面这段代码节选自barchart3.py。Qtrac.Requirer类(没有列出来,但是在Qtrac.py文件里)是个抽象基类,它能像@has_methods类修饰器那样检测相关的类是否实现了所需的接口。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.2 桥接模式..._第3张图片

main()函数设置了一些数据,然后创建了两张条形图,用两种不同的渲染方式分别渲染这些数据。由程序所输出的这两张条形图列在图2.2里,接口与类的关系如图2.3所示。

《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.2 桥接模式..._第4张图片
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.2 桥接模式..._第5张图片

上面这个类实现了条形图绘制接口,并把表示条形图的文本输出到sys.stdout。若想令用户能够配置输出文件,只需对这段代码稍加修改即可,而且在类Unix系统中,还可以拿“绘制方框专用的Unicode字符”(Unicode box drawing character)来画图,并辅以各种颜色,使输出更加美观。
请注意,尽管TextBarRenderer的finalize()什么事都没做,但还是必须写上,因为条形图渲染器的接口里定义了这个方法。
虽说Python的标准库涵盖面非常广(这种设计思路叫做“batteries included”),但奇怪的是,这里面似乎缺了一项重要的功能——找不到专门读写标准位图与矢量图的包。一种办法是调用第三方库,比如,可以使用支持多种图片格式的Pillow库(网址是:github.com/python-imaging/Pillow),也可以使用专门处理某种图片格式的库,甚至还可以用“GUI工具包库”(GUI toolkit library)来做。另一种办法是自己创建图形处理库,我们将在3.12节中讲解这种办法。若只需处理GIF格式的图像,则可使用Tkinter(如果将来随同Python发布的Tk/Tcl是8.6版,那么还支持PNG格式)。
在barchart1.py文件中,ImageBarRenderer类采用cyImage模块来处理图像,若无法使用此模块,则使用Image模块。由于两模块之间差别不大,所以我们把二者统称为“Image模块”。本书的范例代码中有这两个模块的源代码,而且稍后还会讲解它们(3.12节讲解Image模块,5.3节讲解cyImage模块)。为了使程序完整,范例代码里还含有barchart2.py文件,这个版本与barchart1.py的区别在于,它没使用cyImage或Image,而是用Tkinter来处理图像的,本书不会列出此版本的代码。
由于ImageBarRenderer比TextBarRenderer复杂得多,所以我们把该类的静态数据与方法分开,逐个讲解。
screenshot

Image模块用32位无符号整数来表示像素颜色,颜色值由alpha(透明度)、red(红)、green(绿)、blue(蓝)这四个分量组成。模块里有个Image.color_for_name()函数,可根据颜色名称返回与之对应的无符号整数,这个名称既可以是X11的rgb.txt文件中所列的名称(比如"sienna"),也可以是HTML风格的名称(比如"#A0522D")。
上面这段代码定义了条形图中各个条块的颜色。
screenshot

用户可通过__init__()方法的参数来调整条形图中条块的样貌。
screenshot

由于条形图渲染器的接口里定义了initialize(),所以ImageBarRenderer类必须要有上面这个方法才行(同时还必须有draw_caption()、draw_bar()、finalize()方法,它们的代码将在后面列出)。该方法新建了一张图像,其宽度同条块数量与条块宽度之积成正比,其高度与条形图有可能出现的最大高度成正比,其初始颜色是白色。
self.index变量用于记录当前应该绘制条形图里的第几个条块(从0开始算)。
screenshot

由于Image模块没有文本绘制功能,所以无法直接输出caption参数,我们换一种办法使用此参数:以它为基础来确定图像的文件名。
Image模块本身支持两种图像格式,一种是XBM(文件扩展名为.xbm),用于表示黑白图像,另一种是XPM(文件扩展名为.xpm),用于表示彩色图像。如果安装了PyPNG模块(该模块详情参见:pypi.python.org/pypi/pypng),那么Image模块还能支持PNG格式的图像(后缀名为.png)。此处我们选用XPM格式,因为条形图是彩色的,而且这种格式广受支持。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 2.2 桥接模式..._第6张图片

上述方法首先从COLORS序列中选择与当前条块相匹配的颜色(如果条块数量比颜色数还多,那就用当前条块的序号除以颜色总数,根据余数来确定颜色)。然后计算当前条块(也就是self.index)左上角及右下角的坐标,并调用self.image实例(此实例的类型是Image.Image)的rectangle()方法,根据坐标及填充色来绘制矩形。最后递增index,为绘制下个条块做准备。
screenshot

上面这个finalize()方法很简单,保存图像并将其文件名告诉用户。
尽管TextBarRenderer与ImageBarRenderer的实现方式差别很大,但它们均可作为抽象体系与实现体系之间的桥梁。BarCharter类可以分别用两者所提供的具体实现代码来绘制条形图。

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