目录
1.简介
2.结构
3.使用场景
4.实例
5.优缺点
6.与其他模式的关系
7.总结
1.简介
生成器模式(Builder Pattern)是一种创建型设计模式,它允许你通过一步一步构建复杂对象,而不是通过一个包含大量参数的构造函数或方法。该模式特别适用于需要生成的对象具有多个可选属性,且这些属性的组合会导致大量构造函数重载的情况。
生成器模式通过将对象的构建过程分解成多个步骤,使得每一步都可以单独配置。这样不仅提高了代码的可读性和可维护性,还使得构建过程更加灵活和可扩展。
与其他创建型模式不同的是,生成器模式允许你在一个过程中创建一个对象,而无需等待所有部分都准备好。这种模式特别适用于需要构造不同表示或状态的复杂对象时。
2.结构
生成器模式的UML结构图如下所示:
1) 生成器(Builder)接口声明在所有类型生成器中通用的产品构造步骤。
2) 具体生成器(Concrete Builders)提供构造过程的不同实现。具体生成器也可以构造不遵循通用接口的产品。
3) 产品(Products)是最终生成的对象。由不同生成器构造的产品无需属于同一类层次结构或接口。
4) 主管(Director)类定义调用构造步骤的顺序,这样你就可以创建和复用特定的产品配置。
5) 客户端(Client)必须将某个生成器对象与主管类关联。一般情况下,你只需通过主管类构造函数的参数进行一次性关联即可。此后主管类就能使用生成器对象完成后续所有的构造任务。但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。在这种情况下,你在使用主管类生产产品时每次都可以使用不同的生成器。
3.使用场景
1)使用生成器模式可避免“重叠构造函数(telescopicconstructor)”的出现。
假设你的构造函数中有十个可选参数,那么调用该函数会非常不方便;因此,你需要重载这个构造函数,新建几个只有较少参数的简化版。但这些构造函数仍需调用主构造函数,传递一些默认数值来替代省略掉的参数。生成器模式让你可以分步骤生成对象,而且允许你仅使用必须的步骤。应用该模式后,你再也不需要将几十个参数塞进构造函数里了。
2)当你希望使用代码创建不同形式的产品(例如石头或木头房屋)时,可使用生成器模式。
如果你需要创建的各种形式的产品,它们的制造过程相似且仅有细节上的差异,此时可使用生成器模式。基本生成器接口中定义了所有可能的制造步骤,具体生成器将实现这些步骤来制造特定形式的产品。同时,主管类将负责管理制造步骤的顺序。
3)使用生成器构造组合树或其他复杂对象。
当一个对象的构建过程非常复杂,涉及多个步骤和多个部件时,使用生成器模式可以将对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
例如,构建一个汽车对象可能涉及设置车身、引擎、车轮和内饰等多个部件,每个部件都有多种选择。使用生成器模式,可以逐步构建汽车对象,并根据需要选择不同的部件。
4.实例
下面我将展示一个稍微复杂一些的应用场景,即构建一个包含多个可选组件的计算机配置对象。
首先,我们定义一个Computer
类,它代表我们要构建的计算机对象:
#include <iostream>
#include <string>
#include <vector>
#include <memory>class Computer {
private:std::string caseType;std::string cpu;std::string gpu;std::string ram;std::string storage;std::vector<std::string> peripherals;// 禁止直接实例化Computer(const std::string& caseType, const std::string& cpu, const std::string& gpu,const std::string& ram, const std::string& storage,const std::vector<std::string>& peripherals): caseType(caseType), cpu(cpu), gpu(gpu), ram(ram), storage(storage), peripherals(peripherals) {}friend class ComputerBuilder;public:void display() const {std::cout << "Case Type: " << caseType << "\n";std::cout << "CPU: " << cpu << "\n";std::cout << "GPU: " << gpu << "\n";std::cout << "RAM: " << ram << "\n";std::cout << "Storage: " << storage << "\n";std::cout << "Peripherals: ";for (const auto& peripheral : peripherals) {std::cout << peripheral << " ";}std::cout << "\n";}
};
接下来,我们定义一个ComputerBuilder
类,用于逐步构建Computer
对象:
class ComputerBuilder {
protected:std::string caseType;std::string cpu;std::string gpu;std::string ram;std::string storage;std::vector<std::string> peripherals;public:virtual ~ComputerBuilder() = default;ComputerBuilder& setCaseType(const std::string& caseType) {this->caseType = caseType;return *this;}ComputerBuilder& setCpu(const std::string& cpu) {this->cpu = cpu;return *this;}ComputerBuilder& setGpu(const std::string& gpu) {this->gpu = gpu;return *this;}ComputerBuilder& setRam(const std::string& ram) {this->ram = ram;return *this;}ComputerBuilder& setStorage(const std::string& storage) {this->storage = storage;return *this;}ComputerBuilder& addPeripheral(const std::string& peripheral) {peripherals.push_back(peripheral);return *this;}virtual Computer build() const = 0;
};
注意,这里我们将ComputerBuilder
定义为一个抽象类,并声明了一个纯虚函数build
,这样我们就可以有不同的具体实现来构建不同类型的计算机。
现在,我们定义一个具体的GamingComputerBuilder
类来构建游戏计算机:
class GamingComputerBuilder : public ComputerBuilder {
public:GamingComputerBuilder() {// 可以设置一些默认配置setCaseType("ATX Mid Tower");setCpu("Intel i9");// ... 其他默认配置}Computer build() const override {return Computer(caseType, cpu, gpu, ram, storage, peripherals);}
};
在GamingComputerBuilder
中,我们可以设置一些默认配置,或者覆盖父类的方法来提供特定的配置选项。
最后,我们定义一个ComputerDirector
类(可选)来管理构建过程,并展示如何使用这些类来构建一个计算机对象:
class ComputerDirector {
private:ComputerBuilder* builder;public:ComputerDirector(ComputerBuilder* builder) : builder(builder) {}void setBuilder(ComputerBuilder* builder) {this->builder = builder;}Computer constructGamingComputer() {builder->setGpu("NVIDIA RTX 3090");builder->setRam("32GB DDR4");builder->setStorage("2TB SSD");builder->addPeripheral("Gaming Keyboard");builder->addPeripheral("Gaming Mouse");// ... 其他配置return builder->build();}
};int main() {GamingComputerBuilder gamingBuilder;ComputerDirector director(&gamingBuilder);// 或者使用自定义的构建步骤// gamingBuilder.setCaseType("Custom Case");// ... 其他设置// Computer customComputer = gamingBuilder.build();Computer gamingComputer = director.constructGamingComputer();gamingComputer.display();return 0;
}
在这个例子中,我们展示了如何使用生成器模式来构建一个包含多个可选组件的计算机配置对象。通过定义抽象的ComputerBuilder
类和具体的GamingComputerBuilder
类,我们可以灵活地构建不同类型的计算机,并且可以通过ComputerDirector
类来管理构建过程(尽管在这个例子中ComputerDirector
是可选的,但在更复杂的场景中它可能会很有用)。
5.优缺点
优点:
● 你可以分步创建对象,暂缓创建步骤或递归运行创建步骤。
● 生成不同形式的产品时,你可以复用相同的制造代码。
● 单一职责原则。你可以将复杂构造代码从产品的业务逻辑中分离出来。
由于该模式需要新增多个类,因此代码整体复杂程度会有所增加。
6.与其他模式的关系
与工厂模式的关系
- 工厂模式:主要用于生产各种对象,这些对象通常是兄弟类,继承自同一个基类。兄弟子类通过实现基类接口,展现不同的行为,并由工厂函数创建。然而,工厂模式在创建对象时并不关注构造细节,因此在处理复杂对象的生成时可能会显得力不从心。
- 生成器模式:同样用于对象的生成,但更侧重于构造细节,增加了额外的构建流程,以便处理复杂对象的构建需求。生成器模式将对象构造的代码从产品类中抽取出来,放在一个名为生成器的独立对象中,并允许通过不同的生成器来创建不同形式的对象。
- 对比:相较于工厂模式,生成器模式提供了更灵活和详细的对象构造方式。当对象相对简单时,可以使用工厂模式;而当对象复杂且需要详细配置时,生成器模式则更为适用。
与抽象工厂模式的关系
- 抽象工厂模式:专注于生成一系列相关对象,但它在对象构造复杂时,其能力也有限。抽象工厂模式通过定义一个接口,使得客户端可以创建相关或依赖对象的家族,而无需明确指定具体类。
- 生成器模式:虽然也用于创建对象,但更侧重于对象的逐步构造和细节配置。生成器模式允许开发者通过不同的生成器来创建具有不同配置和形式的对象。
- 联系与区别:两者都提供了对象的创建方式,但抽象工厂模式更侧重于对象的家族创建,而生成器模式则更侧重于对象的逐步构造和细节配置。此外,抽象工厂模式通常会立即返回产品,而生成器模式则允许在获取产品前执行一些额外的构造步骤。
与其他模式的关系
- 桥接模式:生成器模式可以与桥接模式结合使用,其中主管类负责抽象工作,而各种不同的生成器负责实现工作。这种结合可以使得对象构造更加灵活和可扩展。
- 原型模式:原型模式通过复制现有对象来创建新对象,而生成器模式则通过逐步构造来创建对象。虽然两者在对象创建方式上有所不同,但在某些情况下,它们可以相互补充,共同实现复杂的对象创建需求。
- 单例模式:抽象工厂、生成器和原型模式都可以用单例来实现,以确保对象的唯一性和全局可访问性。然而,这种实现方式并不常见,因为它可能会增加系统的复杂性和维护成本。
7.总结
生成器模式通过将复杂对象的构建过程与表示分离,使得同样的构建过程可以创建不同的表示。它允许逐步构建对象,通过调用生成器对象的方法来添加或修改对象的部件。