創建型--建造者模式

什麼是建造者模式


建造者模式: 提供了一種精細化控制對象創建過程的模型。假設一個對象必須經過多步操作才能創建成功,我們就必須使用建造者模式。以產生 HTML 頁面爲例:要產生一個 HTML 頁面,我們必須逐步設置:頁面標題文本標題內容主體頁腳。如下圖所示:

在這裏插入圖片描述

上述過程如下: 1. 客戶端發出頁面製作的請求,調用一個**指揮者實例**; 2. **指揮者實例** 調用 **建造者實例** 的接口函數(每一個函數表示一個製作步驟); 3. **建造者實例** 在每一個接口函數裏設置相應的**HTML頁面屬性**; 4. 最後,**頁面屬性** 設置完成之後,由客戶端進行渲染。

在整個 建造者模式 中,涉及到三個角色:事務對象建造者指揮者,我們的最終目的就是創建 事務對象實例

  • 事務對象: 最終需要生成的實例;
  • 建造者: 實現事務對象的具體步驟;
  • 指揮者: 指揮 建造者 創建 事務對象

三者在實現過程中的類關係如下:

在這裏插入圖片描述


建造者模式的使用場景


建造者模式 主要用於以下場景:

  • 精細化控制對象的創建過程;
  • 創建的對象過於複雜,並且要求同一創造過程有不同的表現;

建造者模式與工廠模式的區別


建造者模式工廠模式 都是用來創建對象,但是它們的側重點不一樣;

  • 建造者模式 重視過程控制:由指揮者調用建造者經過多個步驟創建對象,由指揮者顯式調用返回對象
  • 工廠模式 忽略過程,一步到位,直接以工廠函數 返回對象

下面以生產筆記本爲例來說明兩者之間的區別,筆記本的參數爲:

  • 編碼:AG23385193;
  • memory:8G;
  • hdd:500;
  • gpu:GeForce GTX 650 Ti

建造者模式實現筆記本生產

# coding: utf-8

#電腦
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

    # 配置hdd
    def configure_hdd(self, amount):
        self.computer.hdd = amount

    # 配置gpu
    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


def main():
    # 申請一個指揮者
    engineer = HardwareEngineer()
    # 指揮者配置電腦
    engineer.construct_computer(hdd=500, memory=8, gpu='GeForce GTX 650 Ti')
    # 返回實例對象
    computer = engineer.computer #得到一個電腦實例
    print(computer)

if __name__ == '__main__':
    main()

工廠模式實現筆記本生產

# coding: utf-8

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))

if __name__ == '__main__':
    afac = AppleFactory()
    mac_mini = afac.build_computer(MINI14)
    print(mac_mini)

注意: 這裏嵌套了 MacMini14類。這是禁止類直接實例化的一種方式。

上述兩種方式呈現的效果是一樣的。


建造者模式的優點


  • 針對複雜的對象,可以實現 構造過程表現 得分離;指揮者 使用不同風格的 建造者,可以實現對象的不同表現;例如,展示不同的 HTML 頁面風格;
  • 可以精細化的控制對象的創建過程;
  • 可以先創建實例對象,但是稍後訪問(工廠模式是創建即訪問);

建造者模式的例子


例子1:製造 Pizza

我們知道製造 Pizza 是要遵循一定的步驟的:選擇麪糰,選擇配料,裝飾配料,烘焙,等待,開喫等步驟。現在我們要根據用戶的需求製造兩種 Pizza,一種是瑪格麗特 Pizza,一種是奶油 Pizza。兩種 Pizza 的實現步驟是一樣的,但是具體的實現細節卻不一樣。所以,有如下結論:

  • 只存在一個指揮者
  • 存在兩個製造者:瑪格麗特Pizza 製造者,奶油 Pizza 製造者;

定義全局變量

from enum import Enum
import time

# Pizza的過程: [隊列,預備,烘焙,準備]
PizzaProgress = Enum('PizzaProgress', 'queued preparation baking ready')
# Pizza的麪糰
PizzaDough = Enum('PizzaDough', 'thin thick')
# Pizza的配料
PizzaSauce = Enum('PizzaSauce', 'tomato creme_fraiche')
# Pizza的裝飾
PizzaTopping = Enum('PizzaTopping', 'mozzarella double_mozzarella bacon ham mushrooms red_onion oregano')

STEP_DELAY = 3 # 考慮是示例,單位爲秒
  • 常量 STEP_DELAY = 3 用於模擬每一個步驟所花費的時間

定義 Pizza 類

最終的產品是一個 Pizza,由 Pizza 類來描述,不支持直接實例化,該類的注意職責就是數據初始化爲默認值。
唯一的例外是方法 prepare_dough(),將方法 prepare_dough() 方法定義在 Pizza 類而不是 建造者 中,主要考慮到:

  • 最終產品通常最小化,但並不意味着不分配任何職責;
  • 通過組合提供代碼複用率;
# 產品是Pizza
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)) # 麪糰準備好了

構建建造者

在應用中有兩個建造者:一個是瑪格麗特建造者(MargaritaBuilder)、另一個是奶油燻肉建造者(CreamyBaconBuilder),包含的主要步驟爲:

  • prepare_dough(): 準備麪糰;
  • add_sauce(): 準備配料;
  • add_topping(): 添加點綴;
  • bake(): 烘焙;

每個建造者 實現的細節是不一樣的,例如:例如,瑪格麗特比薩的配料是雙層馬蘇裏拉奶酪( mozzarella)和 牛至( oregano),而奶油燻肉比薩的配料是馬蘇裏拉奶酪( mozzarella)、燻肉( bacon)、火腿( ham)、蘑菇( mushrooms)、紫洋蔥( red onion)和牛至( oregano)等。具體代碼實現如下:

# 兩個建造者類的接口函數都是相同的
# 瑪格麗特比薩建造者
class MargaritaBuilder:

    def __init__(self):
        self.pizza = Pizza('margarita') # 建造瑪格麗特Pizza
        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 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 # 烘焙時間

    # 準備麪糰的過程
    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')

構建指揮者

指揮者 用於按照一定的步驟調用 建造者 創建 Pizza,在這個例子中,指揮者 就是服務員(Waiter)。 Waiter類 的核心是construct_pizza 方法,該方法接受一個 建造者 作爲參數,並以正確的順序執行比薩的所有準備步驟。
選擇恰當的建造者(可以在運行時選擇),無需修改指揮者( Waiter)的任何代碼,就能製作不同的比薩。
Waiter類 還包含 pizza() 方法,會向調用者返回最終產品(準備好的 Pizza)。
具體代碼如下:

# 指揮者:服務員
class Waiter:

    def __init__(self):
        self.builder = None

    # 構建比薩
    def construct_pizza(self, builder):
        """把製造者實例作爲參數傳遞進來"""
        self.builder = 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

應用上述代碼

在下述代碼中,還添加了一個輸入檢查的功能,以確保用戶提供有效的輸入。當前案例中這個輸入是映射到一個比薩建造者的字符;- - 輸入字符 m 表示使用 MargaritaBuilder類

  • 輸入字符 c 則使用 CreamyBaconBuilder類

這些映射關係存儲在參數 builder 中。該函數會返回一個元組,如果輸入有效,則元組的第一個元素被設置爲 True, 否則爲False,如下所示

# 確保用戶的輸入是有效的
def validate_style(builders):
    """
    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!

在上述實現的過程中,我們把兩個建造者類存放在一個字典中,用戶可以選擇創建哪一種建造者實例,然後指揮者創調用該建造者實例創建相應的 Pizza。


實例2:簡潔的建造者模式

class Pizza:

    def __init__(self, builder):
        """
        Params: 
            - 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) # self是builder實例對象

if __name__ == '__main__':
    pizza = Pizza.PizzaBuilder().add_garlic().add_extra_cheese().build()
    print(pizza)

我們來仔細分析以下上述代碼:

  1. Pizza.PizzaBuilder():將創建一個 建造者 的實例對象;
  2. Pizza.PizzaBuilder().add_garlic():等效於創建一個建造者建造者 執行添加大蒜操作,返回的是 建造者 實例對象;
  3. Pizza.PizzaBuilder().add_garlic().add_extra_cheese():等效於創建一個建造者建造者 依次執行添加大蒜操作、添加芝士操作,返回的是 建造者 實例對象;
  4. Pizza.PizzaBuilder().add_garlic().add_extra_cheese().build():等效於創建一個建造者建造者 依次執行添加大蒜操作、添加芝士操作,返回的是 Pizza 實例對象;

源碼在這裏


參考資料

  1. 《精通Python設計模式》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章