《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类),这段代码还演示了如何访问嵌套在类中的常量。

相关文章
|
1月前
|
存储 算法 数据处理
使用Python编写高效的数据处理程序
在当今信息爆炸的时代,数据处理变得越来越重要。本文将介绍如何使用Python语言编写高效的数据处理程序,包括利用Python内置的数据结构和函数、优化算法和并行处理等技术,帮助开发者更好地处理和分析大规模数据。
|
29天前
|
Java Python 开发者
Python 学习之路 01基础入门---【Python安装,Python程序基本组成】
线程池详解与异步任务编排使用案例-xian-cheng-chi-xiang-jie-yu-yi-bu-ren-wu-bian-pai-shi-yong-an-li
78 2
Python 学习之路 01基础入门---【Python安装,Python程序基本组成】
|
2天前
|
存储 索引 Python
Python从入门到精通——1.3.1练习编写简单程序
Python从入门到精通——1.3.1练习编写简单程序
|
3天前
|
监控 Python
Python监控主机是否存活,并发报警邮件
Python监控主机是否存活,并发报警邮件
|
7天前
|
数据采集 JavaScript 前端开发
使用Python打造爬虫程序之破茧而出:Python爬虫遭遇反爬虫机制及应对策略
【4月更文挑战第19天】本文探讨了Python爬虫应对反爬虫机制的策略。常见的反爬虫机制包括User-Agent检测、IP限制、动态加载内容、验证码验证和Cookie跟踪。应对策略包括设置合理User-Agent、使用代理IP、处理动态加载内容、验证码识别及维护Cookie。此外,还提到高级策略如降低请求频率、模拟人类行为、分布式爬虫和学习网站规则。开发者需不断学习新策略,同时遵守规则和法律法规,确保爬虫的稳定性和合法性。
|
8天前
|
SQL 安全 Go
如何在 Python 中进行 Web 应用程序的安全性管理,例如防止 SQL 注入?
在Python Web开发中,确保应用安全至关重要,主要防范SQL注入、XSS和CSRF攻击。措施包括:使用参数化查询或ORM防止SQL注入;过滤与转义用户输入抵御XSS;添加CSRF令牌抵挡CSRF;启用HTTPS保障数据传输安全;实现强身份验证和授权系统;智能处理错误信息;定期更新及审计以修复漏洞;严格输入验证;并培训开发者提升安全意识。持续关注和改进是保证安全的关键。
17 0
|
24天前
|
分布式计算 算法 搜索推荐
优化 Python 程序的五大技巧
本文介绍了优化 Python 程序的五大技巧,涵盖了代码结构优化、算法选择、内置函数利用、库的使用以及并行处理等方面。通过对这些技巧的实践,可以提升 Python 程序的性能和效率,从而更好地满足各类应用的需求。
|
25天前
|
Python
python使用tkinter库,封装操作excel为GUI程序
python使用tkinter库,封装操作excel为GUI程序
|
27天前
|
前端开发 JavaScript 数据管理
描述一个使用Python开发Web应用程序的实际项目经验,包括所使用的框架和技术栈。
使用Flask开发Web应用,结合SQLite、Flask-SQLAlchemy进行数据管理,HTML/CSS/JS(Bootstrap和jQuery)构建前端。通过Flask路由处理用户请求,模块化代码提高可维护性。unittest进行测试,开发阶段用内置服务器,生产环境可选WSGI服务器或容器化部署。实现了用户注册登录和数据管理功能,展示Python Web开发的灵活性和效率。
15 4
|
1月前
|
存储 数据库连接 数据处理
Python语言的程序框架
Python语言的程序框架