# 第 2 章 建造者模式

HTML 页面生成问题可以使用建造者模式来解决。该模式中，有两个参与者：建造者（builder）和指挥者（director）。建造者负责创建复杂对象的各个组成部分。指挥者使用一个建造者实例控制建造的过程。使用不同的建造者实例让我们可以创建不同的HTML页面，而无需变更指挥者的代码。

## 2.2 软件的例子

django-widgy 是一个 Django 的第三方树编辑器扩展，可用作内容管理系统（Content Management System，CMS）。它包含一个网页构建器，用来创建具有不同布局的 HTML 页面。

django-query-builder 是另一个基于建造者模式的 Django 第三方扩展库，该扩展库可用于动态地构建 SQL 查询。使用它，我们能够控制一个查询的方方面面，并能创建不同种类的查询，从简单的到非常复杂的都可以。

## 2.3 应用案例

如果我们知道一个对象必须经过多个步骤来创建，并且要求同一个构造过程可以产生不同的表现，就可以使用建造者模式。这种需求存在于许多应用中，例如页面生成器（本章提到的HTML 页面生成器之类）、文档转换器以及用户界面（User Interface， UI）表单创建工具。

有些资料提到建造者模式也可用于解决可伸缩构造函数问题。当我们为支持不同的对象创建方式而不得不创建一个新的构造函数时，可伸缩构造函数问题就发生了，这种情况最终产生许多构造函数和长长的形参列表，难以管理。

这个问题在 Python 中并不存在，因为至少有以下两种方式可以解决这个问题。

* 使用命名形参
* 使用实参列表展开

主要的区别在于工厂模式以单个步骤创建对象，而建造者模式以多个步骤创建对象，并且几乎始终会使用一个指挥者。一些有针对性的建造者模式实现并未使用指挥者，如 Java 的 `StringBuilder`，但这只是例外。

另一个区别是，在工厂模式下，会立即返回一个创建好的对象；而在建造者模式下，仅在需要时客户端代码才显式地请求指挥者返回最终的对象。

### DEMO1

实现一个定制 PC 的建造者模式。

```python
# 最后要创建的实例
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

# 执行创建
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()
```

## 2.4 实现

### DEMO2

以披萨为例的建造者模式：

```python
from enum import Enum
import time

# 以空格为分隔，比如 PizzaProgress.queued=1, PizzaProgress.preparation=2
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))

# 建造者 1
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 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')


# 建造者2
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')


# 指挥者
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):
    pass


def main():
    builders = dict(m=MargaritaBuilder, c=CreamyBaconBuilder)
    valid_input = False
    while not valid_input:
        valid_input, builder = validate_style(builders)
    waiter = Waiter()
    waiter.construct_pizza(builder)
    pizza = waiter.pizza
    print('Enjoy your {}!'.format(pizza))

if __name__ == '__main__':
    main()
```

### 流利的建造者

这是一种有趣的建造者模式变体，这种变体会链式地调用建造者方法，通过将建造者本身定义为内部类并从其每个设置器方法返回自身来实现。方法 `build()` 返回最终的对象。

```python
# 产品类
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)
```

## 2.5 小结

在以下几种情况下，与工厂模式相比，建造者模式是更好的选择。

* 想要创建一个复杂对象（对象由多个部分构成，且对象的创建要经过多个不同的步骤，这些步骤也许还需遵从特定的顺序）
* 要求一个对象能有不同的表现，并希望将对象的构造与表现解耦
* 想要在某个时间点创建对象，但在稍后的时间点再访问


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://l1nwatch.gitbook.io/python-design-mode/di-yi-bu-fen-chuang-jian-xing-mo-shi/di-2-zhang-jian-zao-zhe-mo-shi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
