创建型--建造者模式

文章目录

  • 什么是建造者模式
  • 建造者模式的使用场景
  • 建造者模式与工厂模式的区别
    • 建造者模式实现笔记本生产
    • 工厂模式实现笔记本生产
  • 建造者模式的优点
  • 建造者模式的例子
    • 例子1:制造 Pizza
      • 定义全局变量
      • 定义 Pizza 类
      • 构建建造者
      • 构建指挥者
      • 应用上述代码
    • 实例2:简洁的建造者模式
  • 参考资料

什么是建造者模式


建造者模式: 提供了一种精细化控制对象创建过程的模型。假设一个对象必须经过多步操作才能创建成功,我们就必须使用建造者模式。以产生 HTML 页面为例:要产生一个 HTML 页面,我们必须逐步设置:页面标题文本标题内容主体页脚。如下图所示:

创建型--建造者模式_第1张图片

上述过程如下: 1. 客户端发出页面制作的请求,调用一个**指挥者实例**; 2. **指挥者实例** 调用 **建造者实例** 的接口函数(每一个函数表示一个制作步骤); 3. **建造者实例** 在每一个接口函数里设置相应的**HTML页面属性**; 4. 最后,**页面属性** 设置完成之后,由客户端进行渲染。

在整个 建造者模式 中,涉及到三个角色:事务对象建造者指挥者,我们的最终目的就是创建 事务对象实例

  • 事务对象: 最终需要生成的实例;
  • 建造者: 实现事务对象的具体步骤;
  • 指挥者: 指挥 建造者 创建 事务对象

三者在实现过程中的类关系如下:

创建型--建造者模式_第2张图片


建造者模式的使用场景


建造者模式 主要用于以下场景:

  • 精细化控制对象的创建过程;
  • 创建的对象过于复杂,并且要求同一创造过程有不同的表现;

建造者模式与工厂模式的区别


建造者模式工厂模式 都是用来创建对象,但是它们的侧重点不一样;

  • 建造者模式 重视过程控制:由指挥者调用建造者经过多个步骤创建对象,由指挥者显式调用返回对象
  • 工厂模式 忽略过程,一步到位,直接以工厂函数 返回对象

下面以生产笔记本为例来说明两者之间的区别,笔记本的参数为:

  • 编码: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设计模式》

你可能感兴趣的:(设计模式,建造者模式)