注:关乎对象的创建方式的设计模式就是“创建型设计模式”(creational design pattern)
1.1 抽象工厂模式
“抽象工厂模式”(Abstract Factory Pattern)用来创建复杂的对象,这种对象由许多小对象组成,而这些小对象都属于某个特定的“系列”(family)。
比如说,在GUI 系统里可以设计“抽象控件工厂”(abstract widget factory),并设计三个“具体子类工厂”(concrete subclass factory):MacWidgetFactory、XfceWidgetFactory、WindowsWidgetFactory,它们都创建同一种对象的方法(例如都提供创建按钮的make_button()方法,都提供创建数值调整框的make_spinbox()方法),而具体创建出来的风格则和操作系统平台相符。我们可以编写create_dialog()函数,令其以“工厂实例”(factory instance)为参数来创建OS X、Xfce及Windows风格的对话框,对话框的具体风格取决于传进来的工厂参数。
1.1.1经典的工厂模式
self.svg = SVG_TEXT.format(**locals())
其中,使用**locals()的好处是比较省事,这样就不用再写成SVG_TEXT.format(x=x, y=y, text=text, fontsize=fontsize)了。且从Python3.2开始,还可以把SVG_TEXT.format(**locals())写成SVG_TEXT.format_map(locals()),因为str.format_map()方法会自动执行“映射解包”(mapping unpacking)操作。
1.1.2 Python风格的工厂模式
之前的写法有几个缺点:
a.两个工厂都没有各自的状态,所以根本不需要创建实例。
b.SvgDiagramFactory与DiagramFactory的代码基本上一模一样,只不过前者的make_diagram方法返回SvgText实例,而后者返回Text实例,其他的方法也如此,这会产生许多无谓的重复的代码。最后,DiagramFactory、Diagram、Rectangle、Text类以及SVG系列中与其对应的那些类都放在了“顶级命名空间”(top-level namespace)里
c.给SVG的组建类起名时,需加前缀,使得代码显得不够整洁。
class DiagramFactory: @classmethod def make_diagram(Class, width, height): return Class.Diagram(width, height) @classmethod def make_rectangle(Class, x, y, width, height, fill="white", stroke="black"): return Class.Rectangle(x, y, width, height, fill, stroke) @classmethod def make_text(Class, x, y, text, fontsize=12): return Class.Text(x, y, text, fontsize)
以make 开头的方法现在都变成了“类方法”(class method)。也就是说,调用这些方法时,其首个参数是类,而不像普通的方法那样,首个参数是self。例如,当调用DiagramFactory.make_text()方法时,Class参数就是DiagramFactory,此方法会创建DiagramFactory.Text对象并将其返回。
这种方法使得SvgDiagramFactory子类只需继承DiagramFactory,而不用再去实现那几个make方法了。
def main(): if len(sys.argv) > 1 and sys.argv[1] == "-P": # For regression testing create_diagram(DiagramFactory).save(sys.stdout) create_diagram(SvgDiagramFactory).save(sys.stdout) return textFilename = os.path.join(tempfile.gettempdir(), "diagram.txt") svgFilename = os.path.join(tempfile.gettempdir(), "diagram.svg") txtDiagram = create_diagram(DiagramFactory) txtDiagram.save(textFilename) print("wrote", textFilename) svgDiagram = create_diagram(SvgDiagramFactory) svgDiagram.save(svgFilename) print("wrote", svgFilename)
经过上述改变,main()函数也可以简化,因为现在不需要再创建工厂类的实例了。
附录:
1 #!/usr/bin/env python3 2 # Copyright 漏 2012-13 Qtrac Ltd. All rights reserved. 3 # This program or module is free software: you can redistribute it 4 # and/or modify it under the terms of the GNU General Public License as 5 # published by the Free Software Foundation, either version 3 of the 6 # License, or (at your option) any later version. It is provided for 7 # educational purposes and is distributed in the hope that it will be 8 # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 # General Public License for more details. 11 12 import os 13 import sys 14 import tempfile 15 16 17 def main(): 18 if len(sys.argv) > 1 and sys.argv[1] == "-P": # For regression testing 19 create_diagram(DiagramFactory()).save(sys.stdout) 20 create_diagram(SvgDiagramFactory()).save(sys.stdout) 21 return 22 textFilename = os.path.join(tempfile.gettempdir(), "diagram.txt") 23 svgFilename = os.path.join(tempfile.gettempdir(), "diagram.svg") 24 25 txtDiagram = create_diagram(DiagramFactory()) 26 txtDiagram.save(textFilename) 27 print("wrote", textFilename) 28 29 svgDiagram = create_diagram(SvgDiagramFactory()) 30 svgDiagram.save(svgFilename) 31 print("wrote", svgFilename) 32 33 34 def create_diagram(factory): 35 diagram = factory.make_diagram(30, 7) 36 rectangle = factory.make_rectangle(4, 1, 22, 5, "yellow") 37 text = factory.make_text(7, 3, "Abstract Factory") 38 diagram.add(rectangle) 39 diagram.add(text) 40 return diagram 41 42 43 class DiagramFactory: 44 45 def make_diagram(self, width, height): 46 return Diagram(width, height) 47 48 49 def make_rectangle(self, x, y, width, height, fill="white", 50 stroke="black"): 51 return Rectangle(x, y, width, height, fill, stroke) 52 53 54 def make_text(self, x, y, text, fontsize=12): 55 return Text(x, y, text, fontsize) 56 57 58 59 class SvgDiagramFactory(DiagramFactory): 60 61 def make_diagram(self, width, height): 62 return SvgDiagram(width, height) 63 64 65 def make_rectangle(self, x, y, width, height, fill="white", 66 stroke="black"): 67 return SvgRectangle(x, y, width, height, fill, stroke) 68 69 70 def make_text(self, x, y, text, fontsize=12): 71 return SvgText(x, y, text, fontsize) 72 73 74 BLANK = " " 75 CORNER = "+" 76 HORIZONTAL = "-" 77 VERTICAL = "|" 78 79 80 class Diagram: 81 82 def __init__(self, width, height): 83 self.width = width 84 self.height = height 85 self.diagram = _create_rectangle(self.width, self.height, BLANK) 86 87 88 def add(self, component): 89 for y, row in enumerate(component.rows): 90 for x, char in enumerate(row): 91 self.diagram[y + component.y][x + component.x] = char 92 93 94 def save(self, filenameOrFile): 95 file = None if isinstance(filenameOrFile, str) else filenameOrFile 96 try: 97 if file is None: 98 file = open(filenameOrFile, "w", encoding="utf-8") 99 for row in self.diagram: 100 print("".join(row), file=file) 101 finally: 102 if isinstance(filenameOrFile, str) and file is not None: 103 file.close() 104 105 106 def _create_rectangle(width, height, fill): 107 rows = [[fill for _ in range(width)] for _ in range(height)] 108 for x in range(1, width - 1): 109 rows[0][x] = HORIZONTAL 110 rows[height - 1][x] = HORIZONTAL 111 for y in range(1, height - 1): 112 rows[y][0] = VERTICAL 113 rows[y][width - 1] = VERTICAL 114 for y, x in ((0, 0), (0, width - 1), (height - 1, 0), 115 (height - 1, width -1)): 116 rows[y][x] = CORNER 117 return rows 118 119 120 class Rectangle: 121 122 def __init__(self, x, y, width, height, fill, stroke): 123 self.x = x 124 self.y = y 125 self.rows = _create_rectangle(width, height, 126 BLANK if fill == "white" else "%") 127 128 129 class Text: 130 131 def __init__(self, x, y, text, fontsize): 132 self.x = x 133 self.y = y 134 self.rows = [list(text)] 135 136 137 SVG_START = """<?xml version="1.0" encoding="UTF-8" standalone="no"?> 138 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" 139 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> 140 <svg xmlns="http://www.w3.org/2000/svg" 141 xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" 142 width="{pxwidth}px" height="{pxheight}px">""" 143 144 SVG_END = "</svg> " 145 146 SVG_RECTANGLE = """<rect x="{x}" y="{y}" width="{width}" 147 height="{height}" fill="{fill}" stroke="{stroke}"/>""" 148 149 SVG_TEXT = """<text x="{x}" y="{y}" text-anchor="left" 150 font-family="sans-serif" font-size="{fontsize}">{text}</text>""" 151 152 SVG_SCALE = 20 153 154 155 class SvgDiagram: 156 157 158 def __init__(self, width, height): 159 pxwidth = width * SVG_SCALE 160 pxheight = height * SVG_SCALE 161 self.diagram = [SVG_START.format(**locals())] 162 outline = SvgRectangle(0, 0, width, height, "lightgreen", "black") 163 self.diagram.append(outline.svg) 164 165 166 def add(self, component): 167 self.diagram.append(component.svg) 168 169 170 def save(self, filenameOrFile): 171 file = None if isinstance(filenameOrFile, str) else filenameOrFile 172 try: 173 if file is None: 174 file = open(filenameOrFile, "w", encoding="utf-8") 175 file.write(" ".join(self.diagram)) 176 file.write(" " + SVG_END) 177 finally: 178 if isinstance(filenameOrFile, str) and file is not None: 179 file.close() 180 181 182 class SvgRectangle: 183 184 def __init__(self, x, y, width, height, fill, stroke): 185 x *= SVG_SCALE 186 y *= SVG_SCALE 187 width *= SVG_SCALE 188 height *= SVG_SCALE 189 self.svg = SVG_RECTANGLE.format(**locals()) 190 191 192 class SvgText: 193 194 def __init__(self, x, y, text, fontsize): 195 x *= SVG_SCALE 196 y *= SVG_SCALE 197 fontsize *= SVG_SCALE // 10 198 self.svg = SVG_TEXT.format(**locals()) 199 200 201 if __name__ == "__main__": 202 main()
1 #!/usr/bin/env python3 2 # Copyright 漏 2012-13 Qtrac Ltd. All rights reserved. 3 # This program or module is free software: you can redistribute it 4 # and/or modify it under the terms of the GNU General Public License as 5 # published by the Free Software Foundation, either version 3 of the 6 # License, or (at your option) any later version. It is provided for 7 # educational purposes and is distributed in the hope that it will be 8 # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 # General Public License for more details. 11 12 import os 13 import sys 14 import tempfile 15 16 17 def main(): 18 if len(sys.argv) > 1 and sys.argv[1] == "-P": # For regression testing 19 create_diagram(DiagramFactory).save(sys.stdout) 20 create_diagram(SvgDiagramFactory).save(sys.stdout) 21 return 22 textFilename = os.path.join(tempfile.gettempdir(), "diagram.txt") 23 svgFilename = os.path.join(tempfile.gettempdir(), "diagram.svg") 24 25 txtDiagram = create_diagram(DiagramFactory) 26 txtDiagram.save(textFilename) 27 print("wrote", textFilename) 28 29 svgDiagram = create_diagram(SvgDiagramFactory) 30 svgDiagram.save(svgFilename) 31 print("wrote", svgFilename) 32 33 34 def create_diagram(factory): 35 diagram = factory.make_diagram(30, 7) 36 rectangle = factory.make_rectangle(4, 1, 22, 5, "yellow") 37 text = factory.make_text(7, 3, "Abstract Factory") 38 diagram.add(rectangle) 39 diagram.add(text) 40 return diagram 41 42 43 class DiagramFactory: 44 45 @classmethod 46 def make_diagram(Class, width, height): 47 return Class.Diagram(width, height) 48 49 50 @classmethod 51 def make_rectangle(Class, x, y, width, height, fill="white", 52 stroke="black"): 53 return Class.Rectangle(x, y, width, height, fill, stroke) 54 55 @classmethod 56 def make_text(Class, x, y, text, fontsize=12): 57 return Class.Text(x, y, text, fontsize) 58 59 60 BLANK = " " 61 CORNER = "+" 62 HORIZONTAL = "-" 63 VERTICAL = "|" 64 65 66 class Diagram: 67 68 def __init__(self, width, height): 69 self.width = width 70 self.height = height 71 self.diagram = DiagramFactory._create_rectangle(self.width, 72 self.height, DiagramFactory.BLANK) 73 74 75 def add(self, component): 76 for y, row in enumerate(component.rows): 77 for x, char in enumerate(row): 78 self.diagram[y + component.y][x + component.x] = char 79 80 81 def save(self, filenameOrFile): 82 file = (None if isinstance(filenameOrFile, str) else 83 filenameOrFile) 84 try: 85 if file is None: 86 file = open(filenameOrFile, "w", encoding="utf-8") 87 for row in self.diagram: 88 print("".join(row), file=file) 89 finally: 90 if isinstance(filenameOrFile, str) and file is not None: 91 file.close() 92 93 94 class Rectangle: 95 96 def __init__(self, x, y, width, height, fill, stroke): 97 self.x = x 98 self.y = y 99 self.rows = DiagramFactory._create_rectangle(width, height, 100 DiagramFactory.BLANK if fill == "white" else "%") 101 102 103 class Text: 104 105 def __init__(self, x, y, text, fontsize): 106 self.x = x 107 self.y = y 108 self.rows = [list(text)] 109 110 111 def _create_rectangle(width, height, fill): 112 rows = [[fill for _ in range(width)] for _ in range(height)] 113 for x in range(1, width - 1): 114 rows[0][x] = DiagramFactory.HORIZONTAL 115 rows[height - 1][x] = DiagramFactory.HORIZONTAL 116 for y in range(1, height - 1): 117 rows[y][0] = DiagramFactory.VERTICAL 118 rows[y][width - 1] = DiagramFactory.VERTICAL 119 for y, x in ((0, 0), (0, width - 1), (height - 1, 0), 120 (height - 1, width -1)): 121 rows[y][x] = DiagramFactory.CORNER 122 return rows 123 124 125 class SvgDiagramFactory(DiagramFactory): 126 127 # The make_* class methods are inherited 128 129 SVG_START = """<?xml version="1.0" encoding="UTF-8" standalone="no"?> 130 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" 131 "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> 132 <svg xmlns="http://www.w3.org/2000/svg" 133 xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" 134 width="{pxwidth}px" height="{pxheight}px">""" 135 136 SVG_END = "</svg> " 137 138 SVG_RECTANGLE = """<rect x="{x}" y="{y}" width="{width}" 139 height="{height}" fill="{fill}" stroke="{stroke}"/>""" 140 141 SVG_TEXT = """<text x="{x}" y="{y}" text-anchor="left" 142 font-family="sans-serif" font-size="{fontsize}">{text}</text>""" 143 144 SVG_SCALE = 20 145 146 147 class Diagram: 148 149 def __init__(self, width, height): 150 pxwidth = width * SvgDiagramFactory.SVG_SCALE 151 pxheight = height * SvgDiagramFactory.SVG_SCALE 152 self.diagram = [SvgDiagramFactory.SVG_START.format(**locals())] 153 outline = SvgDiagramFactory.Rectangle(0, 0, width, height, 154 "lightgreen", "black") 155 self.diagram.append(outline.svg) 156 157 158 def add(self, component): 159 self.diagram.append(component.svg) 160 161 162 def save(self, filenameOrFile): 163 file = (None if isinstance(filenameOrFile, str) else 164 filenameOrFile) 165 try: 166 if file is None: 167 file = open(filenameOrFile, "w", encoding="utf-8") 168 file.write(" ".join(self.diagram)) 169 file.write(" " + SvgDiagramFactory.SVG_END) 170 finally: 171 if isinstance(filenameOrFile, str) and file is not None: 172 file.close() 173 174 175 class Rectangle: 176 177 def __init__(self, x, y, width, height, fill, stroke): 178 x *= SvgDiagramFactory.SVG_SCALE 179 y *= SvgDiagramFactory.SVG_SCALE 180 width *= SvgDiagramFactory.SVG_SCALE 181 height *= SvgDiagramFactory.SVG_SCALE 182 self.svg = SvgDiagramFactory.SVG_RECTANGLE.format(**locals()) 183 184 185 class Text: 186 187 def __init__(self, x, y, text, fontsize): 188 x *= SvgDiagramFactory.SVG_SCALE 189 y *= SvgDiagramFactory.SVG_SCALE 190 fontsize *= SvgDiagramFactory.SVG_SCALE // 10 191 self.svg = SvgDiagramFactory.SVG_TEXT.format(**locals()) 192 193 194 if __name__ == "__main__": 195 main()