设计模式篇之 建造者 Builder
目的
构建器(Builder)是一种创建型设计模式,它允许你逐步构建复杂的对象。该模式使你能够使用相同的构建代码来生成不同种类和表现形式的对象。
问题
想象一个复杂的对象,它需要繁琐的、逐步初始化许多字段和嵌套对象。这样的初始化代码通常被隐藏在一个带有大量参数的庞大构造函数中。或者更糟糕的是:分散在客户端代码的各个地方。
例如,我们来思考一下如何创建一个房屋对象。要建造一个简单的房子,你需要建造四面墙和一个地板,安装一扇门,安装一对窗户,并建造一个屋顶。但如果你想要一个更大、更明亮的房子,带有一个后院和其他设施(比如供暖系统、管道和电线),又该怎么办呢?
最简单的解决方案是扩展基础的 House 类,并创建一组子类来涵盖所有参数组合。但最终你会得到相当数量的子类。任何新的参数,比如门廊风格,都将需要进一步扩展这个类层次结构。
还有另一种方法,不需要繁衍子类。你可以在基础的 House 类中创建一个巨大的构造函数,包含所有控制房屋对象的参数。虽然这种方法确实消除了对子类的需求,但它又带来了另一个问题。
在大多数情况下,大多数参数都会被闲置,使得构造函数调用显得非常丑陋。例如,只有少数房子有游泳池,因此与游泳池相关的参数在九次中有十次都是多余的。
解决方案
建造者模式建议将对象的构建代码从其自身类中提取出来,并移动到称为构建器的单独对象中。
该模式将对象构建组织为一系列步骤(如 buildWalls、buildDoor 等)。要创建一个对象,你可以在构建器对象上执行这一系列步骤。重要的是,你不需要调用所有的步骤。你可以只调用那些对于生成特定配置对象所必需的步骤。
在需要构建产品的不同表现形式时,某些构建步骤可能需要不同的实现方式。例如,小屋的墙壁可能是用木头建造的,但城堡的墙壁必须用石头建造。
在这种情况下,你可以创建几个不同的构建器类,它们实现相同的一组构建步骤,但方式不同。然后你可以在构建过程中(即对构建步骤的一系列有序调用)使用这些构建器来生成不同种类的对象。
不同的构建器以不同的方式执行相同的任务。
例如,想象一个用木材和玻璃建造一切的构建器,第二个用石头和铁建造一切,第三个用黄金和钻石建造一切。通过调用相同的一组步骤,你可以从第一个构建器那里得到一所普通房子,从第二个得到一个小城堡,从第三个得到一座宫殿。然而,这只有在调用构建步骤的客户端代码能够通过一个通用接口与构建器交互时才有效。
导演类或指导类(Director)
你可以进一步将用于构建产品的构建步骤调用序列提取到一个单独的类中,称为导演类。导演类定义了执行构建步骤的顺序,而构建器提供了这些步骤的实现。
导演类知道要执行哪些构建步骤才能得到一个可以正常工作的产品
在程序中拥有一个导演类并不是绝对必要的。你总是可以直接从客户端代码中以特定顺序调用构建步骤。然而,导演类可以是一个很好的地方,用于存放各种构建例程,以便在程序中重复使用。
此外,导演类完全隐藏了产品构建的细节,使客户端代码无需了解。客户端只需要将构建器与导演关联起来,通过导演启动构建过程,并从构建器中获取最终结果。
结构

- Builder
构建器接口声明了所有类型的构建器都通用的产品构建步骤。 - Concrete Builders
具体的构建器提供了构建步骤的不同实现方式。具体的构建器可能会生成不符合通用接口的产品。 - Products
产品是构建的结果对象。由不同构建器构建的产品不必属于同一个类层次结构或接口。 - Director
导演类定义了调用构建步骤的顺序,因此你可以创建并重用特定配置的产品。 - Client
客户端必须将其中一个构建器对象与导演关联起来。通常,这通过导演构造函数的参数完成,只进行一次。然后,导演在所有后续的构建中使用该构建器对象。然而,还有一种替代方法,即客户端将构建器对象传递给导演的生产方法。在这种情况下,你可以在使用导演生产时每次都使用不同的构建器。
适用场景
假设你有一个带有十个可选参数的构造函数。调用这样一个构造函数是非常不方便的,因此你重载了构造函数,并创建了几个参数较少的较短版本。这些构造函数仍然引用主构造函数,将一些默认值传递给任何省略的参数。
class Pizza {
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
// ...
在支持方法重载的语言中,例如 C# 或 Java,创建这样的构造函数是可能的。
建造者模式允许你逐步构建对象,只使用那些你真正需要的步骤。在实现了该模式之后,你不再需要在构造函数中塞入几十个参数了。
当你希望你的代码能够创建某个产品(product)的不同表现形式(representations)时,使用构建者(Builder)模式。
当产品的各种表现形式的构建涉及类似的步骤,而这些步骤仅在细节上有所不同时,可以应用建造者模式。
基础构建器接口定义了所有可能的构建步骤,具体的构建器实现了这些步骤以构建产品的特定表现形式。与此同时,导演类指导构建的顺序。
使用构建器(Builder)来构建组合树(Composite trees)或者其他复杂对象。
建造者模式允许你逐步构建产品。你可以在不影响最终产品的情况下延迟执行某些步骤。你甚至可以递归调用步骤,这在需要构建对象树时非常有用。
在运行构建步骤时,构建器不会暴露未完成的产品。这可以防止客户端代码获取不完整的结果。
如何实现
- 确保你可以清晰地定义构建所有可用产品表现形式的通用构建步骤。否则,你将无法继续实现该模式。
- 在基础构建器接口中声明这些步骤。
- 为每种产品表现形式创建一个具体的构建器类,并实现它们的构建步骤。别忘了实现一个方法来获取构建的结果。这个方法不能在构建器接口中声明的原因是,不同的构建器可能会构建不具有通用接口的产品。因此,你不知道这样一个方法的返回类型是什么。然而,如果你处理的是来自单一层次结构的产品,获取方法可以安全地添加到基础接口中。
- 考虑创建一个导演类。它可以封装使用同一个构建器对象构建产品的各种方式。
- 客户端代码创建构建器和导演对象。在开始构建之前,客户端必须将构建器对象传递给导演。通常,客户端只通过导演类构造函数的参数传递一次。导演在所有后续构建中使用构建器对象。还有一个替代方法,即将构建器传递给导演的特定产品构建方法。
- 如果所有产品都遵循相同的接口,可以从导演那里直接获取构建结果。否则,客户端应该从构建器中获取结果。
优缺点
优点
- 你可以逐步构建对象,推迟构建步骤,或者递归地运行步骤
- 在构建产品的不同表现形式时,你可以重复使用相同的构建代码
- 单一职责原则。你可以将复杂的构建代码与产品的业务逻辑隔离
缺点
- 由于建造者模式需要创建多个新的类,因此代码的整体复杂性会增加
和其他模式的关系
- 工厂方法Factory Method
许多设计最初采用工厂方法(Factory Method),这种方式相对不那么复杂,并且可以通过子类来实现更多的定制化。随着设计的演进,会逐渐向抽象工厂(Abstract Factory)、原型(Prototype)或建造者(Builder)模式转变,这些模式更加灵活,但同时也更加复杂。
- 抽象工厂Abstract Factory
建造者模式专注于逐步构建复杂对象,而抽象工厂模式专注于创建一系列相关对象。抽象工厂模式在创建对象后会立即返回产品,而建造者模式允许你在获取产品之前执行一些额外的构建步骤。
- 组合Composite
在创建复杂的组合树(Composite trees)时,你可以使用建造者模式(Builder)。原因是你可以将建造过程的步骤编程为递归工作的方式。
- 单例Singletons
抽象工厂(Abstract Factories)、建造者(Builders)和原型(Prototypes)都可以被实现为单例(Singletons)
Python代码示例
from abc import ABC, abstractmethod
from typing import Any
class Product1():
"""
只有当你的产品相当复杂并且需要大量的配置时,使用建造者模式才有意义。
与其他创建型模式不同,不同的具体构建器可以生成不相关的产品。
换句话说,各种构建器的结果并不总是遵循相同的接口。
"""
def __init__(self):
self.parts = []
def add(self, part: Any) -> None:
self.parts.append(part)
def list_parts(self) -> None:
print(f"Product parts: {', '.join(self.parts)}")
class Builder(ABC):
"""
Builder接口定了一些方法,这些方法是用来创建产品的不同部分
"""
@property
@abstractmethod
def product(self) -> None:
pass
@abstractmethod
def produce_part_a(self) -> None:
pass
@abstractmethod
def produce_part_b(self) -> None:
pass
@abstractmethod
def produce_part_c(self) -> None:
pass
class ConcreteBuilder1(Builder):
"""
具体的构建器类遵循构建器接口,并提供构建步骤的具体实现。
你的程序可能会有几种不同实现方式的构建器变体。
"""
def __init__(self) -> None:
"""
一个新建的构建器实例应该包含一个空白的产品对象,这个对象将被用于后续的组装过程
"""
self.reset()
def reset(self):
self._product = Product1()
@property
def product(self) -> Product1:
"""
具体的构建器应该提供它们自己的方法来检索结果。这是因为各种类型的构建器可能会创建完全不同的产品,
这些产品并不遵循相同的接口。因此,这样的方法不能在基础构建器接口中声明(至少在静态类型编程语言中是这样)。
通常,在将最终结果返回给客户端后,构建器实例应该准备好开始生产另一个产品。
这就是为什么通常会在 `getProduct` 方法体的末尾调用 `reset` 方法。然而,
这种行为并不是强制性的,你可以让你的构建器在丢弃之前的结果之前,等待客户端代码的显式重置调用。
:return:
"""
product = self._product
self.reset()
return product
def produce_part_a(self) -> None:
self._product.add("PartA1")
def produce_part_b(self) -> None:
self._product.add("PartB1")
def produce_part_c(self) -> None:
self._product.add("PartC1")
class Director:
"""
导演类仅负责以特定顺序执行构建步骤。当按照特定顺序或配置生产产品时,
它非常有用。严格来说,导演类是可选的,因为客户端可以直接控制构建器。
"""
def __init__(self) -> None:
self._builder = None
@property
def builder(self) -> Builder:
return self._builder
@builder.setter
def builder(self,builder:Builder) -> None:
"""
导演类可以与客户端代码传递给它的任何构建器实例一起工作。通过这种方式,
客户端代码可以改变新组装产品的最终类型。
:param builder:
:return:
"""
self._builder = builder
"""
导演类(Director)可以使用相同的构建步骤来构建几种不同产品变体
"""
def build_minial_viable_product(self) -> None:
self._builder.produce_part_a()
def build_full_viable_product(self) -> None:
self._builder.produce_part_a()
self._builder.produce_part_b()
self._builder.produce_part_c()
if __name__ == "__main__":
builder = ConcreteBuilder1()
director = Director()
director.builder = builder
director.build_minial_viable_product()
builder.product.list_parts()
director.build_full_viable_product()
builder.product.list_parts()
# 可用不适用Director类,Builder单独使用
builder.produce_part_a()
builder.produce_part_b()
builder.product.list_parts()
Product parts: PartA1
Product parts: PartA1, PartB1, PartC1
Product parts: PartA1, PartB1
更多推荐
所有评论(0)