建造者模式
想象一下,我們想要創(chuàng)建一個由多個部分構成的對象,而且它的構成需要一步接一步地完成。只有當各個部分都創(chuàng)建好,這個對象才算是完整的。這正是建造者設計模式(Builder design pattern)的用武之地。建造者模式將一個復雜對象的構造過程與其表現(xiàn)分離,這樣,同一個構造過程可用于創(chuàng)建多個不同的表現(xiàn)。
我們來看個實際的例子,這可能有助于理解建造者模式的目的。假設我們想要創(chuàng)建一個HTML頁面生成器,HTML頁面的基本結構(構造組件)通常是一樣的:以<html>作為開始和以</html>作為結束,在HTML部分中有<head>和</head>元素,在head部分中又有<title>和</title>元素,等等;但頁面在表現(xiàn)上可以不同。每個頁面有自己的頁面標題、文本標題以及不同的<body>內(nèi)容。此外,頁面通常是經(jīng)過多個步驟創(chuàng)建完成的:有一個函數(shù)添加頁面標題,另一個添加主文本標題,還有一個添加頁腳,等等。僅當一個頁面的結構全部完成后,才能使用一個最終的渲染函數(shù)將該頁面展示在客戶端。我們甚至可以更進一步擴展這個HTML生成器,讓它可以生成一些完全不同的HTML頁面。一個頁面可能包含表格,另一個頁面可能包含圖像庫,還有一個頁面包含聯(lián)系表單,等等。
HTML頁面生成問題可以使用建造者模式來解決。該模式中,有兩個參與者:建造者(builder) 和指揮者(director)。建造者負責創(chuàng)建復雜對象的各個組成部分。在HTML例子中,這些組成部分是頁面標題、文本標題、內(nèi)容主體及頁腳。指揮者使用一個建造者實例控制建造的過程。對于HTML示例,這是指調用建造者的函數(shù)設置頁面標題、文本標題等。使用不同的建造者實例讓我們可以創(chuàng)建不同的HTML頁面,而無需變更指揮者的代碼。
#!/usr/bin/python
# -*- coding : utf-8 -*-
"""
@author: Diogenes Augusto Fernandes Herminio <diofeher@gmail.com>
https://gist.github.com/420905#file_builder_python.py
"""
# Director
class Director(object):
def __init__(self):
self.builder = None
def construct_building(self):
self.builder.new_building()
self.builder.build_floor()
self.builder.build_size()
def get_building(self):
return self.builder.building
# Abstract Builder
class Builder(object):
def __init__(self):
self.building = None
def new_building(self):
self.building = Building()
def build_floor(self):
raise NotImplementedError
def build_size(self):
raise NotImplementedError
# Concrete Builder
class BuilderHouse(Builder):
def build_floor(self):
self.building.floor = 'One'
def build_size(self):
self.building.size = 'Big'
class BuilderFlat(Builder):
def build_floor(self):
self.building.floor = 'More than One'
def build_size(self):
self.building.size = 'Small'
# Product
class Building(object):
def __init__(self):
self.floor = None
self.size = None
def __repr__(self):
return 'Floor: {0.floor} | Size: {0.size}'.format(self)
# Client
if __name__ == "__main__":
director = Director()
director.builder = BuilderHouse()
director.construct_building()
building = director.get_building()
print(building)
director.builder = BuilderFlat()
director.construct_building()
building = director.get_building()
print(building)
### OUTPUT ###
# Floor: One | Size: Big
# Floor: More than One | Size: Small
現(xiàn)實中的例子
快餐店使用的就是建造者設計模式。即使存在多種漢堡包(經(jīng)典款、奶酪漢堡包等)和不同包裝(小盒子、中等大小盒子等),準備一個漢堡包及打包(盒子或紙袋)的流程都是相同的。經(jīng)典款漢堡包和奶酪漢堡包之間的區(qū)別在于表現(xiàn),而不是建造過程。指揮者是出納員,將需要準備什么餐品的指令傳達給工作人員,建造者是工作人員中的個體,關注具體的順序。展示了統(tǒng)一建模語言(UML)的流程圖,說M明當一個兒童套餐下單:
實際軟件的示例:
本章一開始提到的HTML例子,在django-widgy中得到了實際應用。django-widgy是一個 Django的第三方樹編輯器擴展,可用作內(nèi)容管理系統(tǒng)(Content Management System,CMS)。它 包含一個網(wǎng)頁構建器,用來創(chuàng)建具有不同布局的HTML頁面。
django-query-builder是另一個基于建造者模式的Django第三方擴展庫,該擴展庫可用于動態(tài) 地構建SQL查詢。使用它,我們能夠控制一個查詢的方方面面,并能創(chuàng)建不同種類的查詢,從簡 單的到非常復雜的都可以
應用案例
如果我們知道一個對象必須經(jīng)過多個步驟來創(chuàng)建并且要求同一個構造過程可以產(chǎn)生不同的表現(xiàn),就可以使用建造者模式。這種需求存在于許多應用中,例如頁面生成器(本章提到的HTML 頁面生成器之類)、文檔轉換器(請參考[GOF95,第110頁])以及用戶界面(User Interface, UI)表單創(chuàng)建工具(請 參考網(wǎng)頁)。
有些資料提到建造者模式也可用于解決可伸縮構造函數(shù)問題(請參考網(wǎng)頁)。 當我們?yōu)橹С植煌膶ο髣?chuàng)建方式而不得不創(chuàng)建一個新的構造函數(shù)時,可伸縮構造函數(shù)問題就發(fā)生了,這種情況最終產(chǎn)生許多構造函數(shù)和長長的形參列表,難以管理。Stack Overflow網(wǎng)站上列出了一個可伸縮構造函數(shù)的例子(請參考網(wǎng)頁[t.cn/RqBrwzP])。幸運的是,這個問題在Python中并不存在,因為至少有以下兩種方式可以解決這個問題。
- 使用命名形參(請參考網(wǎng)頁)
- 使用實參列表展開(請參考網(wǎng)頁)
在這一點上,建造者模式和工廠模式的差別并不太明確。主要的區(qū)別 在于工廠模式以單個步驟創(chuàng)建對象,而建造者模式以多個步驟創(chuàng)建對象,并且?guī)缀跏冀K會使用一個指揮者。一些有針對性的建造者模式實現(xiàn)并未使用指揮者,如Java的StringBuilder,但這只是例外。
另一個區(qū)別 是,在工廠模式下,會立即返回一個創(chuàng)建好的對象;而在建造者模式下,僅在需要時客戶端代碼才顯式地請求指揮者返回最終的對象(請參考[GOF95,第113頁]和網(wǎng)頁)。
新電腦類比的例子也許有助于區(qū)分建造者模式和工廠模式。假設你想購買一臺新電腦,如果決定購買一臺特定的預配置的電腦型號,例如,最新的蘋果1.4GHz Mac mini,則是在使用工廠模式。所有硬件的規(guī)格都已經(jīng)由制造商預先確定,制造商不用向你咨詢就知道自己該做些什么,它們通常接收的僅僅是單條指令。在代碼級別上,看起來是下面這樣的(apple-factory.py)。
MINI14 = '1.4GHz Mac mini'
class AppleFactory:
class MacMini14:
def __init__(self):
self.memory = 4 # 單位為GB
self.hdd = 500 # 單位為GB
self.gpu = 'Intel HD Graphics 5000'
def __str__(self):
info = ('Model: {}'.format(MINI14),
'Memory: {}GB'.format(self.memory),
'Hard Disk: {}GB'.format(self.hdd),
'Graphics Card: {}'.format(self.gpu))
return '\n'.join(info)
def build_computer(self, model):
if (model == MINI14):
return self.MacMini14()
else:
print("I dont't know how to build {}".format(model))
afac = AppleFactory()
mac_mini = afac.build_computer(MINI14)
print(mac_mini)
將得到的輸出為:
Model: 1.4GHz Mac mini
Memory: 4GB
Hard Disk: 500GB
Graphics Card: Intel HD Graphics 5000
這里嵌套了MacMini14類。這是禁止直接實例化一個類的簡潔方式
另一個選擇是購買一臺定制的PC。假若這樣,使用的即是建造者模式。你是指揮者,向制 造商(建造者)提供指令說明心中理想的電腦規(guī)格。在代碼方面,看起來是下面這樣的:
class Computer:
def __init__(self, serial_number):
self.serial = serial_number
self.memory = None # 單位為GB
self.hdd = None # 單位為GB
self.gpu = None
def __str__(self):
info = ('Memory: {}GB'.format(self.memory),
'Hard Disk: {}GB'.format(self.hdd),
'Graphics Card: {}'.format(self.gpu))
return '\n'.join(info)
class ComputerBuilder:
def __init__(self):
self.computer = Computer('AG23385193')
def configure_memory(self, amount):
self.computer.memory = amount
def configure_hdd(self, amount):
self.computer.hdd = amount
def configure_gpu(self, gpu_model):
self.computer.gpu = gpu_model
class HardwareEngineer:
def __init__(self):
self.builder = None
def construct_computer(self, memory, hdd, gpu):
self.builder = ComputerBuilder()
[step for step in (self.builder.configure_memory(memory),
self.builder.configure_hdd(hdd),
self.builder.configure_gpu(gpu))]
@property
def computer(self):
return self.builder.computer
engineer = HardwareEngineer()
engineer.construct_computer(hdd=500, memory=8, gpu='GeForce GTX 650 Ti')
computer = engineer.computer
print(computer)
得到的輸出將為:
Memory: 8GB
Hard Disk: 500GB
Graphics Card: GeForce GTX 650 Ti
基本的變化是引入了一個建造者ComputerBuilder、一個指揮者HardwareEngineer以及 一步接一步裝配一臺電腦的過程,這樣現(xiàn)在就支持不同的配置了(注意,memory、hdd及gpu是形參,并未預先設置)。如果我們想要支持平板電腦的裝配,那又需要做些什么呢?作為練習來實現(xiàn)它吧。
你或許還想將每臺電腦的serial_number變得都不一樣,因為現(xiàn)在所有電腦的序列號都相同,這是不符合實際情況的。
實現(xiàn)
讓我們來看看如何使用建造者設計模式實現(xiàn)一個比薩訂購的應用。比薩的例子特別有意思,因為準備好一個比薩需經(jīng)過多步操作,且這些操作要遵從特定順序。要添加調味料,你得先準備生面團。要添加配料,你得先添加調味料。并且只有當生面團上放了調味料和配料之后才能開始烤比薩。此外,每個比薩通常要求的烘培時間都不一樣,依賴于生面團的厚度和使用的配料。
先導入要求的模塊,聲明一些Enum參數(shù)(請參考網(wǎng)頁[t.cn/RqBrIpz])以及一個在應用中會使用多次的常量。常量STEP_DELAY用于在準備一個比薩的不同步驟(準備生面團、添加調味料等)之間添加時間延遲,如下所示。
from enum import Enum
PizzaProgress = Enum('PizzaProgress', 'queued preparation baking ready')
PizzaDough = Enum('PizzaDough', 'thin thick')
PizzaSauce = Enum('PizzaSauce', 'tomato creme_fraiche')
PizzaTopping = Enum('PizzaTopping', 'mozzarella double_mozzarella bacon ham mushrooms red_onion oregano')
STEP_DELAY = 3 # 考慮是示例,所以單位為秒
最終的產(chǎn)品是一個比薩,由Pizza類描述。若使用建造者模式,則最終產(chǎn)品(類)并沒有多少職責,因為它不支持直接實例化。建造者會創(chuàng)建一個最終產(chǎn)品的實例,并確保這個實例完全準備好。這就是Pizza類這么短小的緣由。它只是將所有數(shù)據(jù)初始化為合理的默認值,唯一的例外是方法prepare_dough()。將prepare_dough方法定義在Pizza類而不是建造者中,是考慮到以下兩點:
- 為了澄清一點,就是雖然最終產(chǎn)品類通常會最小化,但這并不意味著絕不應該給它分配任何職責
- 為了通過組合提高代碼復用
class Pizza:
def __init__(self, name):
self.name = name
self.dough = None
self.sauce = None
self.topping = []
def __str__(self):
return self.name
def prepare_dough(self, dough):
self.dough = dough
print('preparing the {} dough of your {}...'.format(self.dough.name, self)) time.sleep(STEP_DELAY)
print('done with the {} dough'.format(self.dough.name))
該應用中有兩個建造者:一個制作瑪格麗特比薩(MargaritaBudiler),另一個制作奶油熏肉比薩(CreamyBaconBuilder)。每個建造者都創(chuàng)建一個Pizza實例,并包含遵從比薩制作流程的方法:prepare_dough()、add_sauce、add_topping()和bake()。準確來說,其中的prepare_dough只是對Pizza類中prepare_dough()方法的一層封裝。注意每個建造者是如何處理所有比薩相關細節(jié)的。例如,瑪格麗特比薩的配料是雙層馬蘇里拉奶酪(mozzarella)和牛至(oregano),而奶油熏肉比薩的配料是馬蘇里拉奶酪(mozzarella)、熏肉(bacon)、火腿(ham)、蘑菇(mushrooms)、紫洋蔥(red onion)和牛至(oregano),如下面的代碼所示:
class MargaritaBuilder:
def __init__(self):
self.pizza = Pizza('margarita')
self.progress = PizzaProgress.queued
self.baking_time = 5 # 考慮是示例,單位為秒
def prepare_dough(self):
self.progress = PizzaProgress.preparation
self.pizza.prepare_dough(PizzaDough.thin)
def add_sauce(self):
print('adding the tomato sauce to your margarita...')
self.pizza.sauce = PizzaSauce.tomato
time.sleep(STEP_DELAY)
print('done with the tomato sauce')
def add_topping(self):
print('adding the topping (double mozzarella, oregano) to your margarita')
self.pizza.topping.append([i for i in (PizzaTopping.double_mozzarella, PizzaTopping.oregano)])
time.sleep(STEP_DELAY)
print('done with the topping (double mozzarella, oregano)')
def bake(self):
self.progress = PizzaProgress.baking
print('baking your margarita for {} seconds'.format(self.baking_time))
time.sleep(self.baking_time)
self.progress = PizzaProgress.ready
print('your margarita is ready')
class CreamyBaconBuilder:
def __init__(self):
self.pizza = Pizza('creamy bacon')
self.progress = PizzaProgress.queued
self.baking_time = 7 # 考慮是示例,單位為秒
def prepare_dough(self):
self.progress = PizzaProgress.preparation
self.pizza.prepare_dough(PizzaDough.thick)
def add_sauce(self):
print('adding the crème fra?che sauce to your creamy bacon')
self.pizza.sauce = PizzaSauce.creme_fraiche
time.sleep(STEP_DELAY)
print('done with the crème fra?che sauce')
def add_topping(self):
print('adding the topping (mozzarella,bacon,ham,mushrooms,red onion,oregano) to your creamy bacon')
self.pizza.topping.append([t for t in (PizzaTopping.mozzarella, PizzaTopping.bacon,
PizzaTopping.ham,PizzaTopping.mushrooms,
PizzaTopping.red_onion, PizzaTopping.oregano)])
time.sleep(STEP_DELAY)
print('done with the topping (mozzarella,bacon,ham,mushrooms,red onion,oregano)')
def bake(self):
self.progress = PizzaProgress.baking
print('baking your creamy bacon for {} seconds'.format(self.baking_time))
time.sleep(self.baking_time)
self.progress = PizzaProgress.ready
print('your creamy bacon is ready')
在這個例子中,指揮者就是服務員。Waiter類的核心是construct_pizza方法,該方法接 受一個建造者作為參數(shù),并以正確的順序執(zhí)行比薩的所有準備步驟。選擇恰當?shù)慕ㄔ煺?甚至可 以在運行時選擇),無需修改指揮者(Waiter)的任何代碼,就能制作不同的比薩。Waiter類 還包含pizza()方法,會向調用者返回最終產(chǎn)品(準備好的比薩),如下所示。
class Waiter:
def __init__(self):
self.builder = None
def construct_pizza(self, builder):
self.builder = builder
[step() for step in (builder.prepare_dough, builder.add_sauce, builder.add_topping, builder.bake)]
@property
def pizza(self):
return self.builder.pizza
函數(shù)validate_style()類似于第1章中描述的validate_age()函數(shù),用于確保用戶提供 有效的輸入,當前案例中這個輸入是映射到一個比薩建造者的字符;輸入字符m表示使用 MargaritaBuilder類,輸入字符c則使用CreamyBaconBuilder類。這些映射關系存儲在參數(shù) builder中。該函數(shù)會返回一個元組,如果輸入有效,則元組的第一個元素被設置為True,否則為False,如下所示。
def validate_style(builders):
try:
pizza_style = input('What pizza would you like, [m]argarita or [c]reamy bacon? ')
builder = builders[pizza_style]()
valid_input = True
except KeyError as err:
print('Sorry, only margarita (key m) and creamy bacon (key c) are available')
return (False, None)
return (True, builder)
實現(xiàn)的最后一部分是main()函數(shù)。main()函數(shù)實例化一個比薩建造者,然后指揮者Waiter使用比薩建造者來準備比薩。創(chuàng)建好的比薩可在稍后的時間點交付給客戶端。
def main():
builders = dict(m=MargaritaBuilder, c=CreamyBaconBuilder)
valid_input = False
while not valid_input:
valid_input, builder = validate_style(builders)
print()
waiter = Waiter()
waiter.construct_pizza(builder)
pizza = waiter.pizza
print()
print('Enjoy your {}!'.format(pizza))
將所有代碼片段拼接在一起,示例的完整代碼(builder.py) 如下所示。
from enum import Enum
import time
PizzaProgress = Enum('PizzaProgress', 'queued preparation baking ready')
PizzaDough = Enum('PizzaDough', 'thin thick')
PizzaSauce = Enum('PizzaSauce', 'tomato creme_fraiche')
PizzaTopping = Enum(
'PizzaTopping',
'mozzarella double_mozzarella bacon ham mushrooms red_onion oregano')
STEP_DELAY = 3
class Pizza:
def __init__(self, name):
self.name = name
self.dough = None
self.sauce = None
self.topping = []
def __str__(self):
return self.name
# in seconds for the sake of the
def prepare_dough(self, dough):
self.dough = dough
print('preparing the {} dough of your {}...'.format(
self.dough.name, self))
time.sleep(STEP_DELAY)
print('done with the {} dough'.format(self.dough.name))
class MargaritaBuilder:
def __init__(self):
self.pizza = Pizza('margarita')
self.progress = PizzaProgress.queued
self.baking_time = 5 # in seconds for the sake of the example
def prepare_dough(self):
self.progress = PizzaProgress.preparation
self.pizza.prepare_dough(PizzaDough.thin)
def add_sauce(self):
print('adding the tomato sauce to your margarita...')
self.pizza.sauce = PizzaSauce.tomato
time.sleep(STEP_DELAY)
print('done with the tomato sauce')
def add_topping(self):
print('adding the topping (double mozzarella, oregano) to your margarita')
self.pizza.topping.append([i for i in
(PizzaTopping.double_mozzarella, PizzaTopping.oregano)])
time.sleep(STEP_DELAY)
print('done with the topping (double mozzarrella, oregano)')
def prepare_dough(self):
self.progress = PizzaProgress.preparation
self.pizza.prepare_dough(PizzaDough.thin)
def add_sauce(self):
print('adding the tomato sauce to your margarita...')
self.pizza.sauce = PizzaSauce.tomato
time.sleep(STEP_DELAY)
print('done with the tomato sauce')
def add_topping(self):
print('adding the topping (double mozzarella, oregano) to your margarita')
self.pizza.topping.append([i for i in
(PizzaTopping.double_mozzarella, PizzaTopping.oregano)])
time.sleep(STEP_DELAY)
print('done with the topping (double mozzarrella, oregano)')
def bake(self):
self.progress = PizzaProgress.baking
print('baking your margarita for {} seconds'.format(self.baking_time))
time.sleep(self.baking_time)
self.progress = PizzaProgress.ready
print('your margarita is ready')
class CreamyBaconBuilder:
def __init__(self):
self.pizza = Pizza('creamy bacon')
self.progress = PizzaProgress.queued
self.baking_time = 7 # in seconds for the sake of the example
def prepare_dough(self):
self.progress = PizzaProgress.preparation
self.pizza.prepare_dough(PizzaDough.thick)
def add_sauce(self):
print('adding the crème fra?che sauce to your creamy bacon')
self.pizza.sauce = PizzaSauce.creme_fraiche
time.sleep(STEP_DELAY)
print('done with the crème fra?che sauce')
def add_topping(self):
print('adding the topping (mozzarella, bacon, ham, mushrooms, red onion, oregano) to your creamy bacon')
self.pizza.topping.append([t for t in (PizzaTopping.mozzarella, PizzaTopping.bacon,
PizzaTopping.ham, PizzaTopping.mushrooms,
PizzaTopping.red_onion, PizzaTopping.oregano)])
time.sleep(STEP_DELAY)
print('done with the topping (mozzarella, bacon, ham, mushrooms, red onion, oregano)')
def bake(self):
self.progress = PizzaProgress.baking
print('baking your creamy bacon for {} seconds'.format(self.baking_time))
time.sleep(self.baking_time)
self.progress = PizzaProgress.ready
print('your creamy bacon is ready')
class Waiter:
def __init__(self):
self.builder = None
def construct_pizza(self, builder):
self.builder = builder
[step() for step in (builder.prepare_dough,
builder.add_sauce, builder.add_topping, builder.bake)]
@property
def pizza(self):
return self.builder.pizza
def validate_style(builders):
try:
pizza_style = input('What pizza would you like, [m]argarita or [c]reamy bacon? ')
builder = builders[pizza_style]()
valid_input = True
except KeyError as err:
print('Sorry, only margarita (key m) and creamy bacon (key c) are available')
return (False, None)
return (True, builder)
def main():
builders = dict(m=MargaritaBuilder, c=CreamyBaconBuilder)
valid_input = False
while not valid_input:
valid_input, builder = validate_style(builders)
print()
waiter = Waiter()
waiter.construct_pizza(builder)
pizza = waiter.pizza
print()
print('Enjoy your {}!'.format(pizza))
if __name__ == '__main__':
main()
What pizza would you like, [m]argarita or [c]reamy bacon? m
preparing the thin dough of your margarita...
done with the thin dough
adding the tomato sauce to your margarita...
done with the tomato sauce
adding the topping (double mozzarella, oregano) to your margarita
done with the topping (double mozzarrella, oregano)
baking your margarita for 5 seconds
your margarita is ready
Enjoy your margarita!
程序僅支持兩種比薩類型是挺丟臉的。你自己再來實現(xiàn)一個夏威夷比薩建造者。權衡利弊之后考慮一下是否使用繼承??纯吹湫拖耐谋人_的原料,再決定通過擴展哪個類來實現(xiàn): MargaritaBuilder 或 CreamyBaconBuilder?或許兩者皆擴展(請參考網(wǎng)頁[t.cn/RqBr- XK5])?
在Effective Java (2nd edition)一書中,Joshua Bloch描述了一種有趣的建造者模式變體,這種 變體會鏈式地調用建造者方法,通過將建造者本身定義為內(nèi)部類并從其每個設置器方法返回自身 來實現(xiàn)。方法build()返回最終的對象。這個模式被稱為流利的建造者。以下是其Python實現(xiàn), 由本書的一位評審人友情提供。
class Pizza:
def __init__(self, builder):
self.garlic = builder.garlic
self.extra_cheese = builder.extra_cheese
def __str__(self):
garlic = 'yes' if self.garlic else 'no'
cheese = 'yes' if self.extra_cheese else 'no'
info = ('Garlic: {}'.format(garlic), 'Extra cheese: {}'.format(cheese))
return '\n'.join(info)
class PizzaBuilder:
def __init__(self):
self.extra_cheese = False
self.garlic = False
def add_garlic(self):
self.garlic = True
return self
def add_extra_cheese(self):
self.extra_cheese = True
return self
def build(self):
return Pizza(self)
if __name__ == '__main__':
pizza = Pizza.PizzaBuilder().add_garlic().add_extra_cheese().build()
print(pizza)
你可以嘗試一下把流利的建造者模式應用到比薩的例子。哪個版本你更喜歡?每個版本的優(yōu)勢和劣勢又是什么?
小結
本章中,我們學習了如何使用建造者設計模式??梢栽诠S模式(工廠方法或抽象工廠)不 適用的一些場景中使用建造者模式創(chuàng)建對象。在以下幾種情況下,與工廠模式相比,建造者模式 是更好的選擇。
- 想要創(chuàng)建一個復雜對象(對象由多個部分構成,且對象的創(chuàng)建要經(jīng)過多個不同的步驟, 這些步驟也許還需遵從特定的順序)
- 要求一個對象能有不同的表現(xiàn),并希望將對象的構造與表現(xiàn)解耦 ?
- 想要在某個時間點創(chuàng)建對象,但在稍后的時間點再訪問
我們看到了快餐店如何將建造者模式用于準備食物,兩個第三方Django擴展包(django-widgy 和django-query-builder)各自如何使用建造者模式來生成HTML頁面和動態(tài)的SQL查詢。我們重 點學習了建造者模式與工廠模式之間的區(qū)別,通過對預先配置(工廠)電腦與客戶定制(建造者) 電腦進行訂單類比來理清這兩種設計模式。
在實現(xiàn)部分,我們學習了如何創(chuàng)建一個比薩訂購應用,該應用能處理比薩準備過程的步驟依 賴。本章推薦了很多有趣的練習題,包括實現(xiàn)一個流利的建造者模式。