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

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

1.2 建造者模式

“建造者模式”(Builder Pattern)与抽象工厂模式类似,都可以创建那种需要由其他对象组合而成的复杂对象。而建造者与抽象工厂的区别则在于,它不仅提供了创建复杂对象所需的方法,而且还保存了复杂对象里各个部分的细节。
此模式与抽象工厂模式一样,都能用来拼装对象(也就是用一个或几个简单的对象创建出复杂的对象),但前者尤其适用于需要把复杂对象各部分的细节与其创建流程相分离的场合。
我们用一段“表单”(form)生成程序来演示建造者模式,这段程序既可以用HTML生成网页表单,又可以通过Python及Tkinter生成GUI表单。两种表单都具备图形用户界面,用户可以向其中输入文本,但是表单上的按钮不起作用。图1.2演示了这两种表单,程序源代码位于formbuilder.py文件中。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 1.2 建造者模式..._第1张图片

我们从最顶层的调用语句开始,看看构建每个表单所用的代码。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 1.2 建造者模式..._第2张图片

上面这段代码创建了两个表单,并分别将其写入对应的文件中。在这两种情况下,都会调用同一个表单创建函数(也就是create_login_form()),每次调用时,传入与之相应的建造者对象。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 1.2 建造者模式..._第3张图片

此函数可以创建HTML与Tkinter表单,而且只要有适当的建造者对象,它就能创建出任意形式的表单。builder.add_title()方法用于创建表单的标题,而其他方法则会把表单中的“控件”(widget)添加到给定的行、列位置上。
HtmlFormBuilder与TkFormBuilder都继承自抽象基类AbstractFormBuilder。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 1.2 建造者模式..._第4张图片
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 1.2 建造者模式..._第5张图片

继承自AbstractFormBuilder的类必须实现所有抽象方法。上述代码没有列出add_entry()与add_button()这两个抽象方法,因为它们和add_label()方法非常相似,只是名字不同而已。另外,为了使用abc模块的@abstractmethod“修饰器”(decorator),我们必须把AbstractFormBuilder类的metaclass(元类)设置成abc.ABCMeta。(2.4节将详述修饰器。)

            序列与映射的解包操作

“解包”(unpacking)就是把“序列”(sequence)或“映射”(map)中的每个元素单独提取出来。“序列解包”(sequence unpacking)的一种简单用法是把首个或前几个元素与后面几个元素分别提取出来。比如像下面这样:

如果sequence里至少有三个元素,那么执行完上述代码之后,first == sequence[0],second == sequence[1],rest == sequence[2:]。
最常用到解包操作的地方可能是函数调用语句。如果函数接受一定数量的“位置参数”(positional argument)或某些特定的“关键字参数”(keyword argument),那么可以通过解包操作来提供这些参数。例如:

print_setup()函数需要两个位置参数(名为width及height),并且还有两个可选的关键字参数(名为copies与collate)。上面这段代码在调用函数时没有直接传递参数值,而是先创建了名为args的tuple与名为kwargs的dict,然后通过序列解包操作(args)与映射解包操作(*kwargs)来传递参数。这么做的效果与print_setup(600, 900, copies=2, collate=False)相同。
另一种与函数调用有关的用法是以解包符号来声明参数,使函数能够接受任意数量的位置参数或任意数量的关键字参数,也可以同时接受这两类参数。例如:

上面这段代码所声明的print_args()函数可以接受任意数量的位置参数或关键字参数。在函数中,args参数的类型是tuple,而kwargs参数的类型则是dict。如果还想在print_args()函数里用这些参数来调用别的函数,那么可以把带有解包符号的参数直接传进去(比如:function(*args, kwargs))。映射解包操作还有一种常见用法,就是拿它来调用str.format()方法。比方说,我们可以直接写s.format(locals()),而不用把每个参数都按照key=value的形式手工传进去(此用法的范例可参见1.1.1节的SvgText.__init__()方法)。

如果某个类的metaclass是abc.ABCMeta,那么该类就无法初始化了,只能把它当成抽象基类来用。把C++或Java代码移植到Python时,这么做确实很有用,不过会稍微增加运行期的开销。许多Python程序员都采用一种更为宽松的做法,那就是根本不使用metaclass,而是直接在文档中说明该类只能用作抽象基类。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 1.2 建造者模式..._第6张图片

上面列出了HtmlFormBuilder类的前几行代码。如果构建表单时没有指定标题,那么__init__方法会提供默认的标题。表单里的控件都保存在名为items的字典中,字典的键是由row与column所构成的“二元组”(2-tuple),而值则是控件的HTML代码。
由于基类的add_title()方法是抽象的,所以本类必须重新实现它,不过在实现的时候,我们可以直接调用基类的实现代码。对于HtmlFormBuilder来说,在调用基类的add_title()之前,还必须先用html.escape()函数把title参数处理一下(在Python 3.2及之前的版本中,应该使用xml.sax.saxutil.escape()函数处理)。
add_button()方法(此方法没有列在上述范例代码中)的结构与其他以add开头的方法相似。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 1.2 建造者模式..._第7张图片

HtmlFormBuilder.form()方法创建了一个HTML页面,其中有个

,而里又有个

上面节选了TkFormBuilder类的部分代码。我们把创建表单控件所用的语句(也就是以字符串形式保存的Python代码)放到列表中,每个控件用两条语句来创建。
add_entry()与add_button()方法的代码都没有列在上面,不过其结构与add_label()方法相同。这些方法都是先取得控件的“规范名称”(canonicalized name),然后再声明两个字符串:create字符串中的代码用于构造控件,而layout字符串中的代码则用于调整控件在表单中的位置。最后,这些方法都会把这两个字符串放到statements列表中。
form()方法非常简单,它用title及statements来填充TEMPLATE模板,并把填好的字符串返回。
《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 1.2 建造者模式..._第8张图片

在这份模板代码中,我们先根据title来创建表单类(在本例中,title是Login,所以创建出来的类就叫做LoginForm,参见与)。__init__()方法先设置表单的标题(在本例中,标题是Login,参见),然后用statements中的那些语句来创建表单中的控件,并调整其位置()。
由于代码块最后有if __name__...等语句,所以用这份模板所生成的Python代码可以独立运行。
screenshot

为了使范例代码看起来完整一些,我们把_canonicalize()方法的代码也列出来。从这段代码本身来看,似乎每次执行此函数时都要重新创建正则表达式,但实际上Python有非常庞大的内部缓存,用于存放编译过的正则表达式。只要调用过_canonicalize()方法,那么后续调用时就可以直接从缓存中查找正则表达式了,不用再重新编译。

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