圣诞大减价开始了!

构建器

意图

构建器是一种创造性设计模式,允许您一步一步地构造复杂的对象。该模式允许您使用相同的构造代码生成对象的不同类型和表示形式。

构建器设计模式

问题

想象一个复杂的对象,它需要费力地逐步初始化许多字段和嵌套对象。这样的初始化代码通常隐藏在具有大量参数的庞大构造函数中。或者更糟:分散在客户端代码中。

大量的子类产生了另一个问题

为对象的每个可能配置创建子类可能会使程序过于复杂。

例如,让我们考虑如何创建一个房子对象。要建造一所简单的房子,你需要建造四面墙和地板,安装一扇门,安装一对窗户,并建造一个屋顶。但如果你想要一个更大、更明亮、有后院和其他设施(比如供暖系统、管道和电线)的房子呢?

最简单的解决办法是扩大底座房子类,并创建一组子类来覆盖参数的所有组合。但最终你会得到相当多的子类。任何新的参数,比如门廊样式,都需要进一步扩展这个层次结构。

还有另一种不涉及繁殖子类的方法。你可以在基类中创建一个巨大的构造函数房子使用控制house对象的所有可能参数初始化。虽然这种方法确实消除了对子类的需求,但它产生了另一个问题。

伸缩构造函数

具有大量形参的构造函数有其缺点:并非所有形参都需要。

在大多数情况下,大多数参数将不使用,使构造函数调用非常难看.例如,只有一小部分房子有游泳池,所以与游泳池相关的参数十有八九是无用的。

解决方案

Builder模式建议您从对象自己的类中提取对象构造代码,并将其移动到名为建筑商

应用Builder模式

Builder模式允许您一步一步地构造复杂的对象。构建器不允许其他对象在构建产品时访问产品。

该模式将对象构造组织为一组步骤(buildWallsbuildDoor等)。要创建对象,需要在构建器对象上执行以下一系列步骤。重要的是,您不需要调用所有的步骤。您只能调用产生对象的特定配置所必需的那些步骤。

当您需要构建产品的各种表示形式时,一些构造步骤可能需要不同的实现。例如,小屋的墙壁可以用木头建造,但城堡的墙壁必须用石头建造。

在这种情况下,您可以创建几个不同的构建器类,它们以不同的方式实现相同的构建步骤集。然后,您可以在构建过程中使用这些构建器(即,对构建步骤的有序调用集)来生成不同类型的对象。

不同的构建器以不同的方式执行相同的任务。

例如,想象一个建筑商用木头和玻璃建造一切,第二个建筑商用石头和铁建造一切,第三个建筑商使用黄金和钻石。通过同样的步骤,你从第一个建造者那里得到一座普通的房子,从第二个建造者那里得到一座小城堡,从第三个建造者那里得到一座宫殿。然而,只有当调用构建步骤的客户端代码能够使用公共接口与构建器交互时,这才会起作用。

导演

您可以更进一步,提取一系列对构建器步骤的调用,这些步骤用于将产品构造到名为导演.director类定义了执行构建步骤的顺序,而构建器提供了这些步骤的实现。

主管知道要执行哪些构建步骤才能获得可行的产品。

在你的程序中有一个总监类并不是严格必要的。您总是可以直接从客户端代码中以特定顺序调用构建步骤。然而,director类可能是放置各种构造例程的好地方,这样您就可以在整个程序中重用它们。

此外,director类对客户端代码完全隐藏了产品构造的细节。客户端只需要将构建器与总监关联,使用总监启动构建,并从构建器获得结果。

结构

Builder设计模式的结构 Builder设计模式的结构
  1. 构建器Interface声明了对所有类型的构建器都通用的产品构建步骤。

  2. 混凝土建筑提供构造步骤的不同实现。混凝土建造者可能生产出不遵循通用界面的产品。

  3. 产品是结果对象。由不同构建器构造的产品不必属于相同的类层次结构或接口。

  4. 导演类定义调用构造步骤的顺序,因此您可以创建和重用产品的特定配置。

  5. 客户端必须将一个构建器对象与指示器关联。通常,它只通过指令构造函数的参数执行一次。然后,指令将该构建器对象用于所有进一步的构造。但是,当客户端将构建器对象传递给指令的生产方法时,还有另一种方法。在本例中,每次使用指导程序生成内容时,都可以使用不同的构建器。

伪代码

这个例子构建器模式说明了如何在构建不同类型的产品(如汽车)时重用相同的对象构造代码,并为它们创建相应的手册。

Builder模式示例的结构

逐步构建汽车的例子和适合这些汽车模型的用户指南。

汽车是一个复杂的物体,可以用一百种不同的方法来制造。而不是膨胀类使用一个巨大的构造函数时,我们将汽车组装代码提取到单独的汽车构建器类中。这个类有一组用于配置汽车各个部件的方法。

如果客户端代码需要组装一个特殊的、经过微调的汽车模型,它可以直接与构建器一起工作。另一方面,客户端可以将组装委托给总监类,后者知道如何使用构建器来构造几种最流行的汽车模型。

你可能会感到震惊,但每辆车都需要使用手册(说真的,谁会看呢?)手册描述了这辆车的每一个特性,所以手册中的细节因车型不同而有所不同。这就是为什么对真实的汽车和它们各自的手册重复使用现有的构造过程是有意义的。当然,构建手册与构建汽车是不一样的,这就是为什么我们必须提供另一个专门编写手册的构建器类。这个类实现了与它的汽车构建兄弟相同的构建方法,但是它不是制作汽车部件,而是描述它们。通过将这些构造器传递给同一个director对象,我们可以构造汽车或手册。

最后一部分是获取结果对象。金属汽车和纸质手册虽然相关,但仍然是非常不同的东西。我们不能在没有将指令与具体产品类耦合的情况下在指令中放置获取结果的方法。因此,我们从执行工作的建造者那里获得了建造的结果。

//只有当你的产品非常复杂,需要大量的配置时,使用Builder模式才有意义。//下面的两个产品是相关的,尽管它们没有共同的接口。class Car是//一辆车可以有GPS,旅行电脑和一定数量的//座位。不同型号的汽车(跑车、SUV、//敞篷车)可能安装或启用了不同的功能。每辆车都应该有一本用户手册,对应于汽车的配置,并描述了它的所有功能。//构建器接口指定了创建产品对象不同部分的方法。interface Builder is method reset() method setSeats(…)method setEngine(…)method setTripComputer(…)method setGPS(…)你的//程序可能有几个不同的构建器,每个//实现不同。car //一个新的构建器实例应该包含一个空白的产品//对象,它将在进一步的组装中使用。// reset方法清除正在构建的对象。 method reset() is this.car = new Car() // All production steps work with the same product instance. method setSeats(...) is // Set the number of seats in the car. method setEngine(...) is // Install a given engine. method setTripComputer(...) is // Install a trip computer. method setGPS(...) is // Install a global positioning system. // Concrete builders are supposed to provide their own // methods for retrieving results. That's because various // types of builders may create entirely different products // that don't all follow the same interface. Therefore such // methods can't be declared in the builder interface (at // least not in a statically-typed programming language). // // Usually, after returning the end result to the client, a // builder instance is expected to be ready to start // producing another product. That's why it's a usual // practice to call the reset method at the end of the // `getProduct` method body. However, this behavior isn't // mandatory, and you can make your builder wait for an // explicit reset call from the client code before disposing // of the previous result. method getProduct():Car is product = this.car this.reset() return product // Unlike other creational patterns, builder lets you construct // products that don't follow the common interface. class CarManualBuilder implements Builder is private field manual:Manual constructor CarManualBuilder() is this.reset() method reset() is this.manual = new Manual() method setSeats(...) is // Document car seat features. method setEngine(...) is // Add engine instructions. method setTripComputer(...) is // Add trip computer instructions. method setGPS(...) is // Add GPS instructions. method getProduct():Manual is // Return the manual and reset the builder. // The director is only responsible for executing the building // steps in a particular sequence. It's helpful when producing // products according to a specific order or configuration. // Strictly speaking, the director class is optional, since the // client can control builders directly. class Director is // The director works with any builder instance that the // client code passes to it. This way, the client code may // alter the final type of the newly assembled product. // The director can construct several product variations // using the same building steps. method constructSportsCar(builder: Builder) is builder.reset() builder.setSeats(2) builder.setEngine(new SportEngine()) builder.setTripComputer(true) builder.setGPS(true) method constructSUV(builder: Builder) is // ... // The client code creates a builder object, passes it to the // director and then initiates the construction process. The end // result is retrieved from the builder object. class Application is method makeCar() is director = new Director() CarBuilder builder = new CarBuilder() director.constructSportsCar(builder) Car car = builder.getProduct() CarManualBuilder builder = new CarManualBuilder() director.constructSportsCar(builder) // The final product is often retrieved from a builder // object since the director isn't aware of and not // dependent on concrete builders and products. Manual manual = builder.getProduct()

适用性

使用Builder模式来摆脱“伸缩构造函数”。

假设您有一个具有10个可选参数的构造函数。称呼这样的野兽是很不方便的;因此,您需要重载构造函数,并使用更少的参数创建几个更短的版本。这些构造函数仍然引用主构造函数,将一些默认值传递给任何省略的参数。

class Pizza {Pizza(int size){…}比萨饼(int大小,布尔奶酪){…}比萨饼(int大小,布尔奶酪,布尔意大利辣香肠){…} //…

只有在支持方法重载的语言中才能创建这样的怪物,比如c#或Java。

Builder模式允许您一步一步地构建对象,只使用真正需要的步骤。在实现模式之后,您不再需要在构造函数中塞入大量参数。

当您希望您的代码能够创建某些产品(例如,石头和木头房子)的不同表示形式时,请使用Builder模式。

当构建产品的各种表示形式涉及到类似的步骤,而这些步骤只是在细节上有所不同时,可以应用Builder模式。

基本构建器接口定义了所有可能的构建步骤,具体构建器实现这些步骤来构建产品的特定表示。同时,指导班指导施工顺序。

使用生成器进行构造复合树或其他复杂的物体。

Builder模式允许您逐步构建产品。您可以在不破坏最终产品的情况下推迟某些步骤的执行。您甚至可以递归地调用步骤,这在需要构建对象树时非常方便。

构建程序在运行构造步骤时不会暴露未完成的产品。这可以防止客户端代码获取不完整的结果。

如何实施

  1. 确保您可以清楚地定义构建所有可用产品表示的通用构造步骤。否则,您将无法继续实现该模式。

  2. 在基本构建器接口中声明这些步骤。

  3. 为每个产品表示形式创建一个具体的构建器类,并实现它们的构建步骤。

    不要忘记实现获取构造结果的方法。不能在构建器接口中声明此方法的原因是,各种构建器可能构建没有公共接口的产品。因此,您不知道这样一个方法的返回类型是什么。但是,如果处理的是来自单一层次结构的产品,则可以安全地将抓取方法添加到基本接口。

  4. 考虑创建一个director类。它可以封装使用同一构建器对象构造产品的各种方法。

  5. 客户端代码同时创建构建器和指示器对象。在构造开始之前,客户机必须将一个构造器对象传递给主管。通常,客户端只做一次,通过指令类构造函数的参数。在所有进一步的构造中,指令都使用生成器对象。还有一种替代方法,将构建者传递给主管的特定产品构建方法。

  6. 只有所有产品都遵循相同的界面,才能直接从总监那里得到施工结果。否则,客户端应该从构建器获取结果。

利与弊

  • 您可以一步一步地构造对象,推迟构造步骤或递归地运行步骤。
  • 在构建产品的各种表示形式时,可以重用相同的构造代码。
  • 单一责任原则.您可以将复杂的构造代码与产品的业务逻辑隔离开来。
  • 由于该模式需要创建多个新类,因此代码的总体复杂性增加了。

与其他模式的关系

  • 许多设计从使用开始工厂方法(更简单,更可通过子类定制),并朝着抽象工厂原型,或构建器(更灵活,但更复杂)。

  • 构建器重点是一步一步地构造复杂的对象。抽象工厂擅长创建相关对象的家族。抽象工厂立即返回产品,而构建器让您在获取产品之前运行一些额外的构造步骤。

  • 你可以使用构建器当创建复杂的复合树,因为您可以对其构造步骤进行编程以递归地工作。

  • 你可以结合构建器: director类扮演抽象的角色,而不同的构建器充当实现。

  • 抽象工厂建筑商而且原型可以全部实现为单例

代码示例

c#构建器c++中的构建器Go中的建造者Java构建器PHP构建器Python构建器Ruby中的构建器Rust中的建造者Swift中的建造者TypeScript中的构建器

Baidu
map