《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 1.1 抽象工厂模式

简介:

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

1.1 抽象工厂模式

“抽象工厂模式”(Abstract Factory Pattern)用来创建复杂的对象,这种对象由许多小对象组成,而这些小对象都属于某个特定的“系列”(family)。
比方说,在GUI系统里可以设计“抽象控件工厂”(abstract widget factory),并设计三个“具体子类工厂”(concrete subclass factory):MacWidgetFactory、XfceWidgetFactory、WindowsWidgetFactor,它们都提供创建同一种对象的方法(例如都提供创建按钮的make_button()方法,都提供创建数值调整框的make_spinbox()方法),而具体创建出来的对象的风格则与操作系统平台相符。我们可以编写create_dialog()函数,令其以“工厂实例”(factory instance)为参数来创建OS X、Xfce及Windows风格的对话框,对话框的具体风格取决于传进来的工厂参数。

1.1.1 经典的抽象工厂模式

为了演示抽象工厂模式,我们来写一段程序,用以生成简单的“示意图”(diagram)。这段程序会用到两个“工厂”(factory):一个用来生成纯文本格式的示意图,另一个用来生成SVG(Scalable Vector Graphics,可缩放的矢量图)格式的示意图。图1.1列出了这两种格式。此程序有两种写法,diagram1.py文件按照传统方式来运用抽象工厂模式,而diagram2.py则借助了Python的某些特性,这样写出来的程序比原来更短小、更清晰。这两个版本所生成的示意图都一样。
screenshot

有一些代码是这两个版本都要用的,首先我们来看main()函数。
screenshot

首先创建两个文件(上述范例代码中没有列出相关语句)。接下来,用默认的纯文本工厂()创建示意图,并将其保存。然后,用SVG工厂()来创建同样的示意图,也将其保存。
screenshot
create_diagram函数只有一个参数,就是绘图所用的工厂,该函数用这个工厂创建出所需的示意图。此函数并不知道工厂的具体类型,也无须关心这一点,它只需要知道工厂对象具备创建示意图所需的接口即可。以make开头的那些方法我们放在后面讲。
说完工厂的用法之后,我们来看工厂本身的写法。下面这个工厂类用来绘制纯文本示意图(该工厂也是其他工厂的基类):
screenshot

虽说“抽象工厂模式”的名字里有“抽象”这个词,但实际上我们可以用一个类来身兼二职:这个类既像基类那样定义抽象接口,又像具体类那样提供实现代码。DiagramFactory类就是按照这个思路写出来的。
创建SVG示意图所用的工厂叫做SvgDiagramFactory,该类的前几行代码是:
screenshot

这两个make_diagram方法之间的唯一区别在于:DiagramFactory.make_diagram()方法返回的是Diagram对象,而SvgDiagramFactory.make_diagram()方法返回的是SvgDiagram对象。SvgDiagramFactory里的另外两个方法也是如此(这两个方法没有列在上述范例代码中)。
稍后我们会看到,虽然对应类的接口都一样(比如Diagram与SvgDiagram类的方法名都相同),但是绘制纯文本示意图所用的Diagram、Rectangle、Text等类的实现方式却与SVG示意图所用的SvgDiagram、SvgRectangle、SvgText等类截然不同。这意味着不同系列的类之间不可混搭(比如Rectangle和SvgText就不能放在一张示意图里),相关的工厂类会自行保证这一点。
纯文本Diagram对象用“二维列表”(list of lists)来保存示意图里的数据,这些数据就是空格、+、|、-等字符。纯文本的Rectangle及Text对象也包含由单个字符所构成的二维列表,它们可用来替换大示意图相关位置上的字符(如有必要,还会替换右侧及下方的字符)。
screenshot

上面这几行就是Text类的全部代码了。由于是纯文本,所以无须理会fontSize参数。
screenshot
screenshot

上面是Diagram.add()方法的代码。调用该方法时,component参数可能会是Rectangle或Text对象,该方法会遍历component里的二维列表(也就是components.rows),用其中的数据来替换示意图相应位置上的字符。示意图本身的字符是由Diagram.__init__()方法绘制的(该方法没有列在上面的程序清单中),调用Diagram(width, height)时,__init__()方法会按照给定的宽度与高度用空格把self.diagram填充好。
screenshot

上面列出了SvgText类的全部代码以及该类所依赖的两个常量。顺便说一句,使用locals()的好处是比较省事,这样就不用再写成SVG_TEXT.format(x=x, y=y, text=text, fontsize=fontsize)了。从Python 3.2开始,还可以把SVG_TEXT.format(locals())写成SVG_TEXT.format_map(locals()),因为str.format_map()方法会自动执行“映射解包”(mapping unpacking)操作。(参见1.2节中的补充知识。)
screenshot

SvgDiagram类的每个实例都有一份字符串列表,名叫self.diagram,列表中的每个字符串都表示一行SVG文本。这样一来,向其中加入新组件(比如SvgRectangle或SvgText类型的对象)就变得非常简单了。

1.1.2 Python风格的抽象工厂模式

在上一小节中,我们分别用DiagramFactory和其子类SvgDiagramFactory来创建示意图里的各种组件(比如Diagram、SvgDiagram等),并以此很好地演示了“抽象工厂”这一设计模式。
不过,刚才那种写法有几个缺点。首先,这两个工厂都没有各自的状态,所以根本不需要创建工厂实例。其次,SvgDiagramFactory与DiagramFactory的代码基本上一模一样,只不过前者的make_diagram方法返回SvgText实例,而后者返回Text实例,其他几个方法也如此,这会产生许多无谓的重复代码。最后,DiagramFactory、Diagram、Rectangle、Text类以及SVG系列中与其对应的那些类都放在了“顶级命名空间”(top-level namespace)里。但实际上并没有必要这么做,因为我们只会用到那两个工厂而已。另外,这样做还有个坏处:给SVG示意图的组件类起名时,为了避免名称冲突,必须加上前缀才行(例如表示SVG矩形的那个类要叫做SvgRectangle,而不能直接叫成Rectangle),这样代码就显得不够整洁了。(有种避免名称冲突的办法,就是把每个类都放到各自的模块中,然而这并不能消除重复代码。)
本节将用另外一种写法来弥补上述缺陷。(写好的代码放在diagram2.py文件中。)
第一处改动是把Diagram、Rectangle、Text等类都嵌套到DiagramFactory类里面。修改之后,需要用DiagramFactory.Diagram来引用纯文本的Diagram类,其余类也是如此。创建SVG示意图所用的那些类也可以嵌套到SvgDiagramFactory类里面,这样就不会产生名称冲突了,它们可以和纯文本系列的那些类同名,比方说,表示SVG示意图的那个类也能叫做Diagram,我们可通过SvgDiagramFactory.Diagram来引用它。类所依赖的常量也可以嵌套到工厂里面,于是顶级命名空间里只剩下main()、create_diagram()、DiagramFactory及SvgDiagramFactory了。
screenshot

上面列出了新版DiagramFactory类的前几行代码。以make开头的方法现在都变成了“类方法”(class method)。也就是说,调用这些方法时,其首个参数是类,而不像普通方法那样,首个参数是self。例如,当调用DiagramFactory.make_text()方法时,Class参数就是DiagramFactory,此方法会创建DiagramFactory.Text对象并将其返回。
这种修改方式意味着SvgDiagramFactory子类只需继承DiagramFactory,而不用再去实现那几个make方法了。举例来说,调用SvgDiagramFactory.make_rectangle()方法时,由于SvgDiagramFactory类并没有实现这个方法,所以会执行基类的DiagramFactory.make_rectangle()方法,而执行的时候,Class参数的值是SvgDiagramFactory。这样一来,基类方法自然就能创建出SvgDiagramFactory.Rectangle对象并将其返回了。
screenshot

经过上述修改之后,main()函数也可以简化,因为现在不需要再创建工厂类的实例了。
其余代码和上一小节基本相同,但有个显著的区别,就是在访问相关常数及非工厂类时,必须在名称前面加上工厂类的名字,因为现在它们都嵌套在工厂类里了。
screenshot

上面列出了Text类的代码,该类嵌套在SvgDiagramFactory里面(也就相当于diagram1.py文件里的SvgText类),这段代码还演示了如何访问嵌套在类中的常量。

相关文章
|
6天前
|
设计模式 Java
【设计模式】JAVA Design Patterns——Abstract Factory(抽象工厂模式)
【设计模式】JAVA Design Patterns——Abstract Factory(抽象工厂模式)
|
3天前
|
Linux Python Windows
打包Python程序文件:pyinstaller实现
本文介绍基于Python语言中的pyinstaller模块,将写好的.py格式的Python代码及其所用到的所有第三方库打包,生成.exe格式的可执行文件,从而方便地在其他环境、其他电脑中直接执行这一可执行文件的方法。
|
4天前
|
机器学习/深度学习 网络协议 数据库
Python编程实战:解决常见编程问题
```markdown Python编程入门指南:涵盖文件操作、列表操作、字符串处理、函数编写、异常处理、网络编程和数据库操作等实战案例。通过示例代码,学习如何读写文件、排序列表、转换字符串、创建函数、处理异常、构建TCP服务器及操作SQLite数据库,逐步掌握Python核心技能。 ```
|
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)