赞
踩
本节书摘来自华章出版社《Python编程实战:运用设计模式、并发和程序库创建高质量程序》一 书中的第1章,第1.1节,作者:(美) Mark Summerfield,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
“抽象工厂模式”(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风格的对话框,对话框的具体风格取决于传进来的工厂参数。
为了演示抽象工厂模式,我们来写一段程序,用以生成简单的“示意图”(diagram)。这段程序会用到两个“工厂”(factory):一个用来生成纯文本格式的示意图,另一个用来生成SVG(Scalable Vector Graphics,可缩放的矢量图)格式的示意图。图1.1列出了这两种格式。此程序有两种写法,diagram1.py文件按照传统方式来运用抽象工厂模式,而diagram2.py则借助了Python的某些特性,这样写出来的程序比原来更短小、更清晰。这两个版本所生成的示意图都一样。
有一些代码是这两个版本都要用的,首先我们来看main()函数。
首先创建两个文件(上述范例代码中没有列出相关语句)。接下来,用默认的纯文本工厂()创建示意图,并将其保存。然后,用SVG工厂()来创建同样的示意图,也将其保存。
create_diagram函数只有一个参数,就是绘图所用的工厂,该函数用这个工厂创建出所需的示意图。此函数并不知道工厂的具体类型,也无须关心这一点,它只需要知道工厂对象具备创建示意图所需的接口即可。以make开头的那些方法我们放在后面讲。
说完工厂的用法之后,我们来看工厂本身的写法。下面这个工厂类用来绘制纯文本示意图(该工厂也是其他工厂的基类):
虽说“抽象工厂模式”的名字里有“抽象”这个词,但实际上我们可以用一个类来身兼二职:这个类既像基类那样定义抽象接口,又像具体类那样提供实现代码。DiagramFactory类就是按照这个思路写出来的。
创建SVG示意图所用的工厂叫做SvgDiagramFactory,该类的前几行代码是:
这两个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对象也包含由单个字符所构成的二维列表,它们可用来替换大示意图相关位置上的字符(如有必要,还会替换右侧及下方的字符)。
上面这几行就是Text类的全部代码了。由于是纯文本,所以无须理会fontSize参数。
上面是Diagram.add()方法的代码。调用该方法时,component参数可能会是Rectangle或Text对象,该方法会遍历component里的二维列表(也就是components.rows),用其中的数据来替换示意图相应位置上的字符。示意图本身的字符是由Diagram.__init__()方法绘制的(该方法没有列在上面的程序清单中),调用Diagram(width, height)时,__init__()方法会按照给定的宽度与高度用空格把self.diagram填充好。
上面列出了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节中的补充知识。)
SvgDiagram类的每个实例都有一份字符串列表,名叫self.diagram,列表中的每个字符串都表示一行SVG文本。这样一来,向其中加入新组件(比如SvgRectangle或SvgText类型的对象)就变得非常简单了。
在上一小节中,我们分别用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了。
上面列出了新版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对象并将其返回了。
经过上述修改之后,main()函数也可以简化,因为现在不需要再创建工厂类的实例了。
其余代码和上一小节基本相同,但有个显著的区别,就是在访问相关常数及非工厂类时,必须在名称前面加上工厂类的名字,因为现在它们都嵌套在工厂类里了。
上面列出了Text类的代码,该类嵌套在SvgDiagramFactory里面(也就相当于diagram1.py文件里的SvgText类),这段代码还演示了如何访问嵌套在类中的常量。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。