1. 程式人生 > >(轉自精通Python設計模式)Python設計模式之創建型模式——2.建造者模式

(轉自精通Python設計模式)Python設計模式之創建型模式——2.建造者模式

生成 需要 結構 progress per 展示 有意 odi con

  建造者模式將一個復雜對象的構造過程與其表現分離,這樣,同一個構造過程可用於創建多個不同的表現。

  我們來看個實際的例子,假設我們想要創建一個HMTL頁面生成器,HTML頁面的基本結構(構造組件)通常是一樣的:以<html>開始</html>結束,在HTML部分中有<head>和</head>元素,在head部分中又有<title>和</title>元素,等等;但頁面在表現上可以不同。每個頁面有自己的頁面標題、文本標題以及不同的body內容。此外,頁面通常是經過多個步驟創建完成的:有一個函數添加頁面標題,另一個添加主文本標題,還有一個添加頁腳,等等。僅當一個頁面的結構全部完成後,才能使用一個最終的渲染函數將該頁面展示在客戶端。我們甚至可以更進一步擴展這個HTML生成器,讓它可以生成一些完全不同的HTML頁面。一個頁面可能包含表格,另一個頁面可能包含圖像庫,還有一個頁面包含聯系表單,等等。

  HTML頁面生成問題可以使用建造者模式來解決。該模式中,有兩個參與者:建造者(builder)和指揮者(director) 。建造者負責創建復雜對象的各個組成部分。在HTML例子中,這些組成部分是頁面標題、文本標題、內容主體及頁腳。指揮者使用一個建造者實例控制建造的過程。對於HTML示例,這是指調用建造者的函數設置頁面標題、文本標題等。使用不同的建造者實例讓我們可以創建不同的HTML頁面,而無需變更指揮者的代碼。

  案例

  讓我們來看看如何使用建造者設計模式實現一個比薩訂購的應用。比薩的例子特別有意思,因為準備好一個比薩需經過多步操作,且這些操作要遵從特定順序。要添加調味料,你得先準備生面團。要添加配料,你得先添加調味料。並且只有當生面團上放了調味料和配料之後才能開始烤比薩餅。此外,每個比薩通常要求的烘焙時間都不一樣,依賴於生面團的厚度和使用的配料。

  完整示例代碼(builder.py)如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date    : 2018/7/15 9:59
# @Author  : Yaheng Wang ([email protected])
# @Link    : http://www.wy2160640.github.io
# @Version : 1.0

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

    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

    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 creme fraiche sauce to your creamy bacon‘)
        self.pizza.sauce = PizzaSauce.creme_fraiche
        time.sleep(STEP_DELAY)
        print(‘done with the creme fraiche 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, mushroom, 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()

  小結

  在以下幾種情況下,與工廠模式相比,建造者模式是更好的選擇。

    1.想要創建一個復雜對象(對象由多個部分構成,且對象的創建要經過多個不同的步驟也許還需要遵從特定的順序)

    2.要求一個對象能有不同的表現,並希望將對象的構造與表現解耦

    3.想要在某個時間點創建對象,但在稍後的時間點再訪問

(轉自精通Python設計模式)Python設計模式之創建型模式——2.建造者模式