Python設(shè)計模式之建造者模式

建造者模式

想象一下,我們想要創(chuàng)建一個由多個部分構(gòu)成的對象,而且它的構(gòu)成需要一步接一步地完成获印。只有當(dāng)各個部分都創(chuàng)建好挟伙,這個對象才算是完整的。這正是建造者設(shè)計模式(Builder design pattern)的用武之地研侣。建造者模式將一個復(fù)雜對象的構(gòu)造過程與其表現(xiàn)分離,這樣,同一個構(gòu)造過程可用于創(chuàng)建多個不同的表現(xiàn)潭苞。

我們來看個實際的例子,這可能有助于理解建造者模式的目的真朗。假設(shè)我們想要創(chuàng)建一個HTML頁面生成器此疹,HTML頁面的基本結(jié)構(gòu)(構(gòu)造組件)通常是一樣的:以<html>作為開始和以</html>作為結(jié)束,在HTML部分中有<head>和</head>元素遮婶,在head部分中又有<title>和</title>元素蝗碎,等等;但頁面在表現(xiàn)上可以不同旗扑。每個頁面有自己的頁面標(biāo)題蹦骑、文本標(biāo)題以及不同的<body>內(nèi)容。此外臀防,頁面通常是經(jīng)過多個步驟創(chuàng)建完成的:有一個函數(shù)添加頁面標(biāo)題脊串,另一個添加主文本標(biāo)題辫呻,還有一個添加頁腳,等等琼锋。僅當(dāng)一個頁面的結(jié)構(gòu)全部完成后放闺,才能使用一個最終的渲染函數(shù)將該頁面展示在客戶端。我們甚至可以更進(jìn)一步擴展這個HTML生成器缕坎,讓它可以生成一些完全不同的HTML頁面怖侦。一個頁面可能包含表格,另一個頁面可能包含圖像庫谜叹,還有一個頁面包含聯(lián)系表單匾寝,等等。

HTML頁面生成問題可以使用建造者模式來解決荷腊。該模式中艳悔,有兩個參與者:建造者(builder) 和指揮者(director)。建造者負(fù)責(zé)創(chuàng)建復(fù)雜對象的各個組成部分女仰。在HTML例子中猜年,這些組成部分是頁面標(biāo)題、文本標(biāo)題疾忍、內(nèi)容主體及頁腳乔外。指揮者使用一個建造者實例控制建造的過程。對于HTML示例一罩,這是指調(diào)用建造者的函數(shù)設(shè)置頁面標(biāo)題杨幼、文本標(biāo)題等。使用不同的建造者實例讓我們可以創(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)實中的例子

快餐店使用的就是建造者設(shè)計模式。即使存在多種漢堡包(經(jīng)典款汉嗽、奶酪漢堡包等)和不同包裝(小盒子歹撒、中等大小盒子等),準(zhǔn)備一個漢堡包及打包(盒子或紙袋)的流程都是相同的诊胞。經(jīng)典款漢堡包和奶酪漢堡包之間的區(qū)別在于表現(xiàn)暖夭,而不是建造過程。指揮者是出納員撵孤,將需要準(zhǔn)備什么餐品的指令傳達(dá)給工作人員迈着,建造者是工作人員中的個體,關(guān)注具體的順序邪码。展示了統(tǒng)一建模語言(UML)的流程圖裕菠,說M明當(dāng)一個兒童套餐下單:

實際軟件的示例:

本章一開始提到的HTML例子,在django-widgy中得到了實際應(yīng)用闭专。django-widgy是一個 Django的第三方樹編輯器擴展奴潘,可用作內(nèi)容管理系統(tǒng)(Content Management System旧烧,CMS)。它 包含一個網(wǎng)頁構(gòu)建器画髓,用來創(chuàng)建具有不同布局的HTML頁面掘剪。

django-query-builder是另一個基于建造者模式的Django第三方擴展庫,該擴展庫可用于動態(tài) 地構(gòu)建SQL查詢奈虾。使用它夺谁,我們能夠控制一個查詢的方方面面,并能創(chuàng)建不同種類的查詢肉微,從簡 單的到非常復(fù)雜的都可以

應(yīng)用案例

如果我們知道一個對象必須經(jīng)過多個步驟來創(chuàng)建并且要求同一個構(gòu)造過程可以產(chǎn)生不同的表現(xiàn)匾鸥,就可以使用建造者模式。這種需求存在于許多應(yīng)用中碉纳,例如頁面生成器(本章提到的HTML 頁面生成器之類)勿负、文檔轉(zhuǎn)換器(請參考[GOF95,第110頁])以及用戶界面(User Interface劳曹, UI)表單創(chuàng)建工具(請 參考網(wǎng)頁)奴愉。

有些資料提到建造者模式也可用于解決可伸縮構(gòu)造函數(shù)問題(請參考網(wǎng)頁)。 當(dāng)我們?yōu)橹С植煌膶ο髣?chuàng)建方式而不得不創(chuàng)建一個新的構(gòu)造函數(shù)時厚者,可伸縮構(gòu)造函數(shù)問題就發(fā)生了躁劣,這種情況最終產(chǎn)生許多構(gòu)造函數(shù)和長長的形參列表迫吐,難以管理库菲。Stack Overflow網(wǎng)站上列出了一個可伸縮構(gòu)造函數(shù)的例子(請參考網(wǎng)頁[t.cn/RqBrwzP])。幸運的是志膀,這個問題在Python中并不存在熙宇,因為至少有以下兩種方式可以解決這個問題。

在這一點上溉浙,建造者模式和工廠模式的差別并不太明確烫止。主要的區(qū)別 在于工廠模式以單個步驟創(chuàng)建對象,而建造者模式以多個步驟創(chuàng)建對象戳稽,并且?guī)缀跏冀K會使用一個指揮者馆蠕。一些有針對性的建造者模式實現(xiàn)并未使用指揮者,如Java的StringBuilder惊奇,但這只是例外互躬。

另一個區(qū)別 是,在工廠模式下颂郎,會立即返回一個創(chuàng)建好的對象吼渡;而在建造者模式下,僅在需要時客戶端代碼才顯式地請求指揮者返回最終的對象(請參考[GOF95乓序,第113頁]和網(wǎng)頁)寺酪。

新電腦類比的例子也許有助于區(qū)分建造者模式和工廠模式坎背。假設(shè)你想購買一臺新電腦,如果決定購買一臺特定的預(yù)配置的電腦型號寄雀,例如得滤,最新的蘋果1.4GHz Mac mini,則是在使用工廠模式咙俩。所有硬件的規(guī)格都已經(jīng)由制造商預(yù)先確定耿戚,制造商不用向你咨詢就知道自己該做些什么,它們通常接收的僅僅是單條指令阿趁。在代碼級別上膜蛔,看起來是下面這樣的(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是形參纷责,并未預(yù)先設(shè)置)捍掺。如果我們想要支持平板電腦的裝配,那又需要做些什么呢?作為練習(xí)來實現(xiàn)它吧再膳。

你或許還想將每臺電腦的serial_number變得都不一樣挺勿,因為現(xiàn)在所有電腦的序列號都相同,這是不符合實際情況的喂柒。

實現(xiàn)

讓我們來看看如何使用建造者設(shè)計模式實現(xiàn)一個比薩訂購的應(yīng)用不瓶。比薩的例子特別有意思,因為準(zhǔn)備好一個比薩需經(jīng)過多步操作灾杰,且這些操作要遵從特定順序蚊丐。要添加調(diào)味料,你得先準(zhǔn)備生面團艳吠。要添加配料麦备,你得先添加調(diào)味料。并且只有當(dāng)生面團上放了調(diào)味料和配料之后才能開始烤比薩昭娩。此外凛篙,每個比薩通常要求的烘培時間都不一樣,依賴于生面團的厚度和使用的配料栏渺。

先導(dǎo)入要求的模塊呛梆,聲明一些Enum參數(shù)(請參考網(wǎng)頁[t.cn/RqBrIpz])以及一個在應(yīng)用中會使用多次的常量。常量STEP_DELAY用于在準(zhǔn)備一個比薩的不同步驟(準(zhǔn)備生面團磕诊、添加調(diào)味料等)之間添加時間延遲填物,如下所示。

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)品(類)并沒有多少職責(zé)责语,因為它不支持直接實例化炮障。建造者會創(chuàng)建一個最終產(chǎn)品的實例,并確保這個實例完全準(zhǔn)備好坤候。這就是Pizza類這么短小的緣由胁赢。它只是將所有數(shù)據(jù)初始化為合理的默認(rèn)值,唯一的例外是方法prepare_dough()白筹。將prepare_dough方法定義在Pizza類而不是建造者中智末,是考慮到以下兩點:

  • 為了澄清一點谅摄,就是雖然最終產(chǎn)品類通常會最小化,但這并不意味著絕不應(yīng)該給它分配任何職責(zé)
  • 為了通過組合提高代碼復(fù)用
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))

該應(yīng)用中有兩個建造者:一個制作瑪格麗特比薩(MargaritaBudiler)由蘑,另一個制作奶油熏肉比薩(CreamyBaconBuilder)闽寡。每個建造者都創(chuàng)建一個Pizza實例,并包含遵從比薩制作流程的方法:prepare_dough()尼酿、add_sauce爷狈、add_topping()和bake()。準(zhǔn)確來說裳擎,其中的prepare_dough只是對Pizza類中prepare_dough()方法的一層封裝淆院。注意每個建造者是如何處理所有比薩相關(guān)細(xì)節(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')

在這個例子中,指揮者就是服務(wù)員膛薛。Waiter類的核心是construct_pizza方法听隐,該方法接 受一個建造者作為參數(shù),并以正確的順序執(zhí)行比薩的所有準(zhǔn)備步驟哄啄。選擇恰當(dāng)?shù)慕ㄔ煺?甚至可 以在運行時選擇)雅任,無需修改指揮者(Waiter)的任何代碼,就能制作不同的比薩咨跌。Waiter類 還包含pizza()方法沪么,會向調(diào)用者返回最終產(chǎn)品(準(zhǔ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ù),用于確保用戶提供 有效的輸入记焊,當(dāng)前案例中這個輸入是映射到一個比薩建造者的字符逸月;輸入字符m表示使用 MargaritaBuilder類,輸入字符c則使用CreamyBaconBuilder類遍膜。這些映射關(guān)系存儲在參數(shù) builder中碗硬。該函數(shù)會返回一個元組,如果輸入有效瓢颅,則元組的第一個元素被設(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使用比薩建造者來準(zhǔn)備比薩。創(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)一個夏威夷比薩建造者。權(quán)衡利弊之后考慮一下是否使用繼承倘零〕猓看看典型夏威夷比薩的原料,再決定通過擴展哪個類來實現(xiàn): MargaritaBuilder 或 CreamyBaconBuilder呈驶?或許兩者皆擴展(請參考網(wǎng)頁[t.cn/RqBr- XK5])拷泽?

在Effective Java (2nd edition)一書中,Joshua Bloch描述了一種有趣的建造者模式變體袖瞻,這種 變體會鏈?zhǔn)降卣{(diào)用建造者方法跌穗,通過將建造者本身定義為內(nèi)部類并從其每個設(shè)置器方法返回自身 來實現(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īng)用到比薩的例子娄昆。哪個版本你更喜歡佩微?每個版本的優(yōu)勢和劣勢又是什么?

小結(jié)

本章中,我們學(xué)習(xí)了如何使用建造者設(shè)計模式∑辏可以在工廠模式(工廠方法或抽象工廠)不 適用的一些場景中使用建造者模式創(chuàng)建對象。在以下幾種情況下奶卓,與工廠模式相比,建造者模式 是更好的選擇撼玄。

  • 想要創(chuàng)建一個復(fù)雜對象(對象由多個部分構(gòu)成夺姑,且對象的創(chuàng)建要經(jīng)過多個不同的步驟, 這些步驟也許還需遵從特定的順序)
  • 要求一個對象能有不同的表現(xiàn)掌猛,并希望將對象的構(gòu)造與表現(xiàn)解耦 ?
  • 想要在某個時間點創(chuàng)建對象盏浙,但在稍后的時間點再訪問

我們看到了快餐店如何將建造者模式用于準(zhǔn)備食物,兩個第三方Django擴展包(django-widgy 和django-query-builder)各自如何使用建造者模式來生成HTML頁面和動態(tài)的SQL查詢荔茬。我們重 點學(xué)習(xí)了建造者模式與工廠模式之間的區(qū)別废膘,通過對預(yù)先配置(工廠)電腦與客戶定制(建造者) 電腦進(jìn)行訂單類比來理清這兩種設(shè)計模式。

在實現(xiàn)部分慕蔚,我們學(xué)習(xí)了如何創(chuàng)建一個比薩訂購應(yīng)用丐黄,該應(yīng)用能處理比薩準(zhǔn)備過程的步驟依 賴。本章推薦了很多有趣的練習(xí)題坊萝,包括實現(xiàn)一個流利的建造者模式孵稽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末许起,一起剝皮案震驚了整個濱河市十偶,隨后出現(xiàn)的幾起案子菩鲜,更是在濱河造成了極大的恐慌,老刑警劉巖惦积,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件接校,死亡現(xiàn)場離奇詭異,居然都是意外死亡狮崩,警方通過查閱死者的電腦和手機蛛勉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來睦柴,“玉大人诽凌,你說我怎么就攤上這事√沟校” “怎么了侣诵?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狱窘。 經(jīng)常有香客問我杜顺,道長,這世上最難降的妖魔是什么蘸炸? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任躬络,我火速辦了婚禮,結(jié)果婚禮上搭儒,老公的妹妹穿的比我還像新娘穷当。我一直安慰自己,他們只是感情好淹禾,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布膘滨。 她就那樣靜靜地躺著,像睡著了一般稀拐。 火紅的嫁衣襯著肌膚如雪火邓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天德撬,我揣著相機與錄音铲咨,去河邊找鬼。 笑死蜓洪,一個胖子當(dāng)著我的面吹牛纤勒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隆檀,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摇天,長吁一口氣:“原來是場噩夢啊……” “哼粹湃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起泉坐,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤为鳄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后腕让,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孤钦,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年纯丸,在試婚紗的時候發(fā)現(xiàn)自己被綠了偏形。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡觉鼻,死狀恐怖俊扭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坠陈,我是刑警寧澤萨惑,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站畅姊,受9級特大地震影響咒钟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜若未,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一朱嘴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粗合,春花似錦萍嬉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至供屉,卻和暖如春行冰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伶丐。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工悼做, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哗魂。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓肛走,卻偏偏與公主長得像,于是被迫代替她去往敵國和親录别。 傳聞我的和親對象是個殘疾皇子朽色,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容