(轉自精通Python設計模式)Python設計模式之創建型模式——2.建造者模式
建造者模式將一個復雜對象的構造過程與其表現分離,這樣,同一個構造過程可用於創建多個不同的表現。
我們來看個實際的例子,假設我們想要創建一個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.建造者模式