【C++实战㊶】C++建造者模式:复杂对象构建的秘密武器

目录

  • 一、建造者模式的概念
    • [1.1 建造者模式的定义](#1.1 建造者模式的定义)
    • [1.2 建造者模式的适用场景](#1.2 建造者模式的适用场景)
    • [1.3 建造者模式的结构](#1.3 建造者模式的结构)
  • 二、建造者模式的实战应用
    • [2.1 建造者模式的实现代码](#2.1 建造者模式的实现代码)
    • [2.2 建造者模式与工厂模式的区别](#2.2 建造者模式与工厂模式的区别)
    • [2.3 建造者模式的简化实现](#2.3 建造者模式的简化实现)
  • 三、建造者模式的实战技巧
    • [3.1 建造者模式的默认配置与自定义配置](#3.1 建造者模式的默认配置与自定义配置)
    • [3.2 建造者模式的线程安全处理](#3.2 建造者模式的线程安全处理)
    • [3.3 建造者模式的扩展](#3.3 建造者模式的扩展)
  • 四、实战项目:复杂文档生成系统(建造者版)
    • [4.1 项目需求](#4.1 项目需求)
    • [4.2 建造者模式实现文档分步构建](#4.2 建造者模式实现文档分步构建)
    • [4.3 不同格式文档(HTML、Word)的生成测试](#4.3 不同格式文档(HTML、Word)的生成测试)

一、建造者模式的概念

1.1 建造者模式的定义

建造者模式(Builder Pattern)是一种创建型设计模式,它将复杂对象的构建过程与表示分离,使得同样的构建过程可以创建不同的表示。在实际编程中,我们常常会遇到一些对象,它们的创建过程十分复杂,包含多个步骤以及众多的属性设置。如果直接在构造函数中完成这些复杂的操作,会使构造函数变得臃肿不堪,难以维护和理解。

例如,在游戏开发中创建一个角色对象,这个角色可能有独特的外貌、装备、技能等多个属性。如果将所有这些属性的初始化都放在构造函数中,代码可能会变得冗长且混乱。建造者模式通过将这些复杂的构建步骤抽象出来,封装在一个或多个建造者类中,使得对象的构建过程更加清晰和可维护。这样,我们可以在不改变构建过程的前提下,轻松地创建出具有不同表示形式的角色对象,比如战士、法师、刺客等,每个角色都有其独特的属性配置,但构建的基本流程是一致的。这种分离的方式提高了代码的灵活性和可扩展性,符合软件设计中的单一职责原则和开闭原则。

1.2 建造者模式的适用场景

  • 创建复杂对象:当对象的构建需要多个步骤,且步骤之间存在依赖关系时,建造者模式能有效简化构建过程。以创建一个复杂的文档对象为例,该文档可能包含标题、正文、图片、表格等多个部分,每个部分的创建都有其特定的逻辑和顺序。使用建造者模式,可以将每个部分的创建逻辑封装在相应的建造者方法中,通过指挥者按照正确的顺序调用这些方法,从而轻松构建出完整的文档对象。
  • 对象参数众多且有默认值:当对象的构造函数需要接收大量参数,其中很多参数又有默认值时,使用建造者模式可以避免构造函数参数列表过于冗长,提高代码的可读性和可维护性。例如,创建一个配置对象,该对象包含数据库连接信息、日志级别、缓存策略等多个配置项,其中大部分配置项都有默认值。使用建造者模式,我们可以通过链式调用的方式,只设置那些需要改变的配置项,而不必在构造函数中传入所有参数。
  • 构造过程需独立于创建类:如果希望对象的构造过程独立于创建它的类,以便在不同的场景中复用相同的构建过程,建造者模式是一个很好的选择。比如,在一个电商系统中,创建订单对象的过程可能涉及到用户信息、商品信息、支付方式、配送地址等多个方面的处理。将这些处理逻辑封装在建造者类中,可以使订单的创建过程与具体的业务场景解耦,方便在不同的业务模块中复用订单创建的逻辑。

1.3 建造者模式的结构

建造者模式主要包含以下四个核心角色:

  • 指挥者(Director):负责控制对象的构建过程,它知道如何按照特定的顺序调用建造者的方法来创建对象。指挥者并不直接参与对象的构建,而是协调各个建造者之间的工作。例如,在建造房屋的场景中,指挥者可以是一个建筑项目经理,他根据设计图纸和施工计划,指挥工人(具体建造者)按照基础、墙体、屋顶等顺序来建造房屋。
  • 建造者接口(Builder):定义了创建产品各个部分的抽象方法,它是具体建造者必须实现的接口。这个接口规定了创建复杂对象所需的各个步骤,为具体建造者提供了统一的规范。比如,对于房屋建造者接口,可能会定义 buildFoundation(建造地基)、buildWalls(建造墙体)、buildRoof(建造屋顶)等抽象方法。
  • 具体建造者(ConcreteBuilder):实现了建造者接口,负责具体创建产品的各个部分,并将这些部分组装成最终的产品。每个具体建造者都有自己独特的实现方式,用于创建不同类型或风格的产品。例如,有专门建造别墅的具体建造者,它在实现建造者接口的方法时,会采用适合别墅的建筑材料和工艺来建造地基、墙体和屋顶;还有建造普通公寓的具体建造者,其实现方式会有所不同。
  • 产品(Product):即最终创建出来的复杂对象,它由多个部分组成,这些部分是通过具体建造者的构建方法逐步创建和组装而成的。在房屋建造的例子中,产品就是建造完成的房屋,它包含了地基、墙体、屋顶等各个部分,这些部分协同工作,共同构成了一个完整的房屋对象。

这四个角色相互协作,通过指挥者调用具体建造者的方法,按照建造者接口定义的规范,逐步创建出复杂的产品对象,使得对象的构建过程清晰、灵活且易于维护。

二、建造者模式的实战应用

2.1 建造者模式的实现代码

下面通过一个创建电脑对象的例子来展示建造者模式的实现代码。在这个例子中,电脑包含 CPU、内存、硬盘等部件,我们通过建造者模式来分步构建电脑对象,并由指挥者统一控制构建流程。

cpp 复制代码
#include <iostream>
#include <string>
#include <memory>

// 产品:电脑
class Computer {
private:
    std::string cpu;
    std::string ram;
    std::string hdd;

public:
    // 设置CPU
    void setCPU(const std::string& c) {
        cpu = c;
    }
    // 设置内存
    void setRAM(const std::string& r) {
        ram = r;
    }
    // 设置硬盘
    void setHDD(const std::string& d) {
        hdd = d;
    }

    // 显示电脑配置
    void showConfig() const {
        std::cout << "电脑配置:" << std::endl;
        std::cout << "CPU: " << cpu << std::endl;
        std::cout << "内存: " << ram << std::endl;
        std::cout << "硬盘: " << hdd << std::endl;
    }
};

// 抽象建造者
class ComputerBuilder {
public:
    // 纯虚函数,构建CPU
    virtual void buildCPU() = 0;
    // 纯虚函数,构建内存
    virtual void buildRAM() = 0;
    // 纯虚函数,构建硬盘
    virtual void buildHDD() = 0;
    // 获取构建完成的电脑对象
    virtual std::unique_ptr<Computer> getResult() = 0;
    virtual ~ComputerBuilder() = default;
};

// 具体建造者:游戏本建造者
class GamingPCBuilder : public ComputerBuilder {
private:
    std::unique_ptr<Computer> computer;

public:
    GamingPCBuilder() {
        // 创建一个新的电脑对象
        computer = std::make_unique<Computer>();
    }

    void buildCPU() override {
        // 设置高性能CPU
        computer->setCPU("Intel i9-13900K");
    }

    void buildRAM() override {
        // 设置大容量高速内存
        computer->setRAM("32GB DDR5");
    }

    void buildHDD() override {
        // 设置高速固态硬盘
        computer->setHDD("1TB NVMe SSD");
    }

    std::unique_ptr<Computer> getResult() override {
        // 返回构建好的游戏本
        return std::move(computer);
    }
};

// 具体建造者:办公本建造者
class OfficePCBuilder : public ComputerBuilder {
private:
    std::unique_ptr<Computer> computer;

public:
    OfficePCBuilder() {
        // 创建一个新的电脑对象
        computer = std::make_unique<Computer>();
    }

    void buildCPU() override {
        // 设置均衡型CPU
        computer->setCPU("Intel i5-13400");
    }

    void buildRAM() override {
        // 设置够用的内存
        computer->setRAM("16GB DDR4");
    }

    void buildHDD() override {
        // 设置普通固态硬盘
        computer->setHDD("512GB SATA SSD");
    }

    std::unique_ptr<Computer> getResult() override {
        // 返回构建好的办公本
        return std::move(computer);
    }
};

// 指挥者
class Director {
public:
    // 构建电脑的方法,接收一个建造者对象
    std::unique_ptr<Computer> construct(ComputerBuilder* builder) {
        // 调用建造者的方法按顺序构建电脑的各个部件
        builder->buildCPU();
        builder->buildRAM();
        builder->buildHDD();
        // 获取构建好的电脑对象并返回
        return builder->getResult();
    }
};

在上述代码中:

  • Computer类是产品类,包含了电脑的各个部件属性以及设置部件和显示配置的方法。
  • ComputerBuilder是抽象建造者,定义了构建电脑各个部件的纯虚函数以及获取最终产品的纯虚函数。
  • GamingPCBuilder和OfficePCBuilder是具体建造者,分别实现了抽象建造者的方法,用于构建游戏本和办公本,它们内部创建了Computer对象,并根据各自的需求设置部件属性。
  • Director是指挥者,它的construct方法接收一个建造者对象,按照固定的顺序调用建造者的方法来构建电脑,最后返回构建好的电脑对象。

使用示例如下:

cpp 复制代码
int main() {
    Director director;

    // 构建游戏本
    GamingPCBuilder gamingBuilder;
    std::unique_ptr<Computer> gamingPC = director.construct(&gamingBuilder);
    std::cout << "=== 游戏本 ===" << std::endl;
    gamingPC->showConfig();

    // 构建办公本
    OfficePCBuilder officeBuilder;
    std::unique_ptr<Computer> officePC = director.construct(&officeBuilder);
    std::cout << "=== 办公本 ===" << std::endl;
    officePC->showConfig();

    return 0;
}

在main函数中,我们创建了Director对象和具体建造者对象(GamingPCBuilder和OfficePCBuilder),然后通过Director的construct方法来构建不同类型的电脑,并调用showConfig方法显示电脑配置。

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

  • 关注重点不同
    • 建造者模式:关注的是复杂对象的构建过程,将对象的构建过程与表示分离,强调一步一步地精确构造对象。例如在构建电脑时,建造者模式会详细处理 CPU、内存、硬盘等各个部件的选择和安装顺序,注重构建过程中的细节。
    • 工厂模式:侧重于对象的创建,它更关注的是如何根据不同的条件创建出不同类型的对象,而不关心对象的具体构建过程。比如简单工厂模式中,根据传入的参数创建不同形状的图形对象(圆形、方形等),它只负责创建对象,不关心图形内部的属性设置等细节。
  • 创建方式不同
    • 建造者模式:创建对象的过程是分步进行的,每个步骤都有明确的职责,通过一系列的方法调用逐步构建出完整的对象。如前面的电脑示例,先构建 CPU,再构建内存,最后构建硬盘等,每个部件的构建都由相应的方法完成。
    • 工厂模式:通常是通过一个工厂类的单一方法来创建对象,将对象的创建逻辑封装在工厂类中。例如在简单工厂模式中,ShapeFactory类的getShape方法根据传入的形状类型参数,直接返回对应的形状对象,创建过程相对简单直接。
  • 适用场景不同
    • 建造者模式:适用于创建复杂对象,对象的构建需要多个步骤,且步骤之间存在依赖关系,或者对象的属性较多且有默认值的情况。比如创建一个复杂的文档对象,该文档包含标题、正文、图片、表格等多个部分,使用建造者模式可以清晰地定义每个部分的创建逻辑和顺序。
    • 工厂模式:适用于创建简单对象,或者创建对象的逻辑比较简单,只需要根据不同的条件返回不同类型的对象的场景。例如创建不同类型的日志记录器,根据配置文件中的日志级别来创建对应的日志记录器对象,使用工厂模式可以方便地管理对象的创建过程。

2.3 建造者模式的简化实现

在实际应用中,为了简化建造者模式的使用,可以采用链式调用建造者的方式。这种方式通过让建造者的每个设置方法都返回自身,从而可以在一行代码中连续调用多个设置方法,使代码更加简洁和易读。

以下是使用链式调用简化后的电脑建造者代码:

cpp 复制代码
#include <iostream>
#include <string>
#include <memory>

// 产品:电脑
class Computer {
private:
    std::string cpu;
    std::string ram;
    std::string hdd;

public:
    // 设置CPU
    void setCPU(const std::string& c) {
        cpu = c;
    }
    // 设置内存
    void setRAM(const std::string& r) {
        ram = r;
    }
    // 设置硬盘
    void setHDD(const std::string& d) {
        hdd = d;
    }

    // 显示电脑配置
    void showConfig() const {
        std::cout << "电脑配置:" << std::endl;
        std::cout << "CPU: " << cpu << std::endl;
        std::cout << "内存: " << ram << std::endl;
        std::cout << "硬盘: " << hdd << std::endl;
    }
};

// 建造者
class ComputerBuilder {
private:
    std::unique_ptr<Computer> computer;

public:
    ComputerBuilder() {
        computer = std::make_unique<Computer>();
    }

    // 设置CPU并返回自身,支持链式调用
    ComputerBuilder& buildCPU(const std::string& c) {
        computer->setCPU(c);
        return *this;
    }

    // 设置内存并返回自身,支持链式调用
    ComputerBuilder& buildRAM(const std::string& r) {
        computer->setRAM(r);
        return *this;
    }

    // 设置硬盘并返回自身,支持链式调用
    ComputerBuilder& buildHDD(const std::string& d) {
        computer->setHDD(d);
        return *this;
    }

    // 获取构建完成的电脑对象
    std::unique_ptr<Computer> build() {
        return std::move(computer);
    }
};

在上述代码中,ComputerBuilder类的buildCPU、buildRAM和buildHDD方法都返回*this,即自身的引用,这样就可以在调用这些方法时进行链式操作。

使用示例如下:

cpp 复制代码
int main() {
    // 使用链式调用构建电脑
    std::unique_ptr<Computer> customPC = ComputerBuilder()
           .buildCPU("Intel i7-12700K")
           .buildRAM("16GB DDR4")
           .buildHDD("512GB NVMe SSD")
           .build();

    std::cout << "=== 自定义电脑 ===" << std::endl;
    customPC->showConfig();

    return 0;
}

在main函数中,我们通过链式调用buildCPU、buildRAM和buildHDD方法,在一行代码中完成了电脑对象的构建,最后调用build方法获取构建好的电脑对象并显示其配置。

链式调用建造者的优势主要有以下几点:

  • 代码简洁:减少了代码量,使对象的构建过程更加紧凑和直观,避免了多次创建和引用建造者对象的繁琐操作。
  • 易读性强:通过链式调用,可以清晰地看到对象的构建步骤和属性设置顺序,提高了代码的可读性和可维护性。
  • 灵活性高:用户可以根据自己的需求灵活选择需要设置的属性,而不必按照固定的顺序调用所有的设置方法,增强了代码的灵活性和可扩展性。

三、建造者模式的实战技巧

3.1 建造者模式的默认配置与自定义配置

在实际应用中,很多时候我们希望对象有一些默认的配置,同时也允许用户根据具体需求进行自定义配置。在建造者模式中,实现这一功能非常方便。

对于默认配置,我们可以在具体建造者类的构造函数中进行设置。例如,在前面的电脑建造者示例中,如果我们希望办公本有一些默认的配置:

cpp 复制代码
class OfficePCBuilder : public ComputerBuilder {
private:
    std::unique_ptr<Computer> computer;

public:
    OfficePCBuilder() {
        computer = std::make_unique<Computer>();
        // 设置默认的CPU
        computer->setCPU("Intel i5-13400");
        // 设置默认的内存
        computer->setRAM("16GB DDR4");
        // 设置默认的硬盘
        computer->setHDD("512GB SATA SSD");
    }

    void buildCPU() override {
        // 用户自定义时会覆盖默认设置
    }

    void buildRAM() override {
        // 用户自定义时会覆盖默认设置
    }

    void buildHDD() override {
        // 用户自定义时会覆盖默认设置
    }

    std::unique_ptr<Computer> getResult() override {
        return std::move(computer);
    }
};

在上述代码中,OfficePCBuilder的构造函数设置了办公本的默认 CPU、内存和硬盘配置。当用户需要自定义配置时,只需要调用相应的build方法即可。

例如,用户想要将办公本的内存升级为 32GB DDR4,可以这样实现:

cpp 复制代码
int main() {
    OfficePCBuilder officeBuilder;
    officeBuilder.buildRAM("32GB DDR4");
    std::unique_ptr<Computer> customOfficePC = officeBuilder.getResult();
    std::cout << "=== 自定义办公本 ===" << std::endl;
    customOfficePC->showConfig();

    return 0;
}

在这个例子中,用户通过调用officeBuilder.buildRAM("32GB DDR4");来覆盖了默认的内存配置,而其他部分仍然使用默认配置。这样,通过在建造者类中合理设置默认值和提供可覆盖的方法,我们可以轻松实现对象的默认配置与自定义配置功能,提高了代码的灵活性和适用性。

3.2 建造者模式的线程安全处理

在多线程环境下,建造者模式可能会出现一些问题。例如,当多个线程同时调用建造者的方法来构建对象时,可能会导致数据竞争和不一致的情况。

假设我们有一个多线程环境下使用建造者模式构建数据库连接对象的场景。数据库连接对象需要配置数据库地址、用户名、密码等信息。如果多个线程同时调用建造者的build方法来构建连接对象,可能会出现以下问题:

cpp 复制代码
// 数据库连接类
class DatabaseConnection {
private:
    std::string address;
    std::string username;
    std::string password;

public:
    void setAddress(const std::string& addr) {
        address = addr;
    }
    void setUsername(const std::string& user) {
        username = user;
    }
    void setPassword(const std::string& pwd) {
        password = pwd;
    }

    void connect() {
        // 连接数据库的逻辑
        std::cout << "Connecting to " << address << " as " << username << " with password " << password << std::endl;
    }
};

// 建造者类
class DatabaseConnectionBuilder {
private:
    DatabaseConnection connection;

public:
    DatabaseConnectionBuilder& buildAddress(const std::string& addr) {
        connection.setAddress(addr);
        return *this;
    }

    DatabaseConnectionBuilder& buildUsername(const std::string& user) {
        connection.setUsername(user);
        return *this;
    }

    DatabaseConnectionBuilder& buildPassword(const std::string& pwd) {
        connection.setPassword(pwd);
        return *this;
    }

    DatabaseConnection build() {
        return connection;
    }
};

在多线程环境下,可能会出现一个线程在设置地址时,另一个线程同时设置用户名,导致最终的连接对象配置混乱。

为了解决这个问题,我们可以采用加锁的方式来保证线程安全。例如,使用 C++11 中的std::mutex来实现线程安全的建造者:

cpp 复制代码
#include <mutex>

class DatabaseConnection {
    // 同之前定义
};

class DatabaseConnectionBuilder {
private:
    DatabaseConnection connection;
    std::mutex mtx;

public:
    DatabaseConnectionBuilder& buildAddress(const std::string& addr) {
        std::lock_guard<std::mutex> lock(mtx);
        connection.setAddress(addr);
        return *this;
    }

    DatabaseConnectionBuilder& buildUsername(const std::string& user) {
        std::lock_guard<std::mutex> lock(mtx);
        connection.setUsername(user);
        return *this;
    }

    DatabaseConnectionBuilder& buildPassword(const std::string& pwd) {
        std::lock_guard<std::mutex> lock(mtx);
        connection.setPassword(pwd);
        return *this;
    }

    DatabaseConnection build() {
        std::lock_guard<std::mutex> lock(mtx);
        return connection;
    }
};

在上述代码中,我们在DatabaseConnectionBuilder类中添加了一个std::mutex成员变量mtx,并在每个build方法和最终的build方法中使用std::lock_guard<std::mutex>来自动管理锁的生命周期。这样,当一个线程进入某个build方法时,会自动获取锁,其他线程只能等待锁释放后才能进入,从而保证了数据的一致性和线程安全。

3.3 建造者模式的扩展

在实际应用中,我们可能需要构建多种不同类型的产品,这时就需要对建造者模式进行扩展以支持多产品构建。

假设我们有一个图形绘制系统,需要绘制不同类型的图形,如圆形和矩形,并且每种图形都有自己的属性和绘制方式。我们可以通过扩展建造者模式来实现:

cpp 复制代码
#include <iostream>
#include <string>

// 抽象产品:图形
class Shape {
public:
    virtual void draw() const = 0;
    virtual ~Shape() = default;
};

// 具体产品:圆形
class Circle : public Shape {
private:
    int radius;

public:
    Circle(int r) : radius(r) {}

    void draw() const override {
        std::cout << "Drawing a circle with radius " << radius << std::endl;
    }
};

// 具体产品:矩形
class Rectangle : public Shape {
private:
    int width;
    int height;

public:
    Rectangle(int w, int h) : width(w), height(h) {}

    void draw() const override {
        std::cout << "Drawing a rectangle with width " << width << " and height " << height << std::endl;
    }
};

// 抽象建造者
class ShapeBuilder {
public:
    virtual void build() = 0;
    virtual std::unique_ptr<Shape> getShape() = 0;
    virtual ~ShapeBuilder() = default;
};

// 具体建造者:圆形建造者
class CircleBuilder : public ShapeBuilder {
private:
    int radius;
    std::unique_ptr<Shape> circle;

public:
    CircleBuilder(int r) : radius(r) {}

    void build() override {
        circle = std::make_unique<Circle>(radius);
    }

    std::unique_ptr<Shape> getShape() override {
        return std::move(circle);
    }
};

// 具体建造者:矩形建造者
class RectangleBuilder : public ShapeBuilder {
private:
    int width;
    int height;
    std::unique_ptr<Shape> rectangle;

public:
    RectangleBuilder(int w, int h) : width(w), height(h) {}

    void build() override {
        rectangle = std::make_unique<Rectangle>(width, height);
    }

    std::unique_ptr<Shape> getShape() override {
        return std::move(rectangle);
    }
};

// 指挥者
class ShapeDirector {
public:
    std::unique_ptr<Shape> construct(ShapeBuilder* builder) {
        builder->build();
        return builder->getShape();
    }
};

在上述代码中:

  • Shape是抽象产品类,定义了绘制图形的抽象方法draw。
  • Circle和Rectangle是具体产品类,分别实现了Shape的draw方法,用于绘制圆形和矩形。
  • ShapeBuilder是抽象建造者类,定义了构建图形的抽象方法build和获取图形的抽象方法getShape。
  • CircleBuilder和RectangleBuilder是具体建造者类,分别实现了抽象建造者的方法,用于构建圆形和矩形。
  • ShapeDirector是指挥者类,通过调用建造者的方法来构建图形。

使用示例如下:

cpp 复制代码
int main() {
    ShapeDirector director;

    // 构建圆形
    CircleBuilder circleBuilder(5);
    std::unique_ptr<Shape> circle = director.construct(&circleBuilder);
    circle->draw();

    // 构建矩形
    RectangleBuilder rectangleBuilder(10, 5);
    std::unique_ptr<Shape> rectangle = director.construct(&rectangleBuilder);
    rectangle->draw();

    return 0;
}

在main函数中,我们通过ShapeDirector分别构建了圆形和矩形,并调用它们的draw方法进行绘制。通过这种方式,我们成功扩展了建造者模式,使其能够支持多种不同类型产品的构建。

四、实战项目:复杂文档生成系统(建造者版)

4.1 项目需求

在许多实际应用场景中,如办公软件、文档管理系统等,经常需要生成包含多种元素的复杂文档。本项目旨在开发一个复杂文档生成系统,该系统能够生成包含标题、正文、图片、表格等多种元素的文档。具体需求如下:

  • 标题:文档应包含一个标题,用于概括文档的主要内容,标题需要有合适的格式设置,如字体、字号、加粗等。
  • 正文:支持输入多行正文内容,正文内容应具有正常的排版格式,如段落缩进、行间距等。
  • 图片:可以在文档中插入图片,图片需要指定路径,并且能够根据文档布局进行适当的缩放和位置调整。
  • 表格:能够创建表格,表格可以包含任意数量的行和列,每一个单元格都可以输入内容,并且可以对表格的边框、单元格背景颜色等进行设置。

通过满足这些需求,该文档生成系统可以广泛应用于各种需要生成复杂文档的场景,如报告生成、合同制作、新闻发布等。

4.2 建造者模式实现文档分步构建

下面使用 C++ 代码来实现基于建造者模式的文档分步构建。

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <memory>

// 文档类,作为产品
class Document {
private:
    std::string title;
    std::string content;
    std::vector<std::string> images;
    std::vector<std::vector<std::string>> table;

public:
    void setTitle(const std::string& t) {
        title = t;
    }
    void addContent(const std::string& c) {
        content += c + "\n";
    }
    void addImage(const std::string& img) {
        images.push_back(img);
    }
    void addRowToTable(const std::vector<std::string>& row) {
        table.push_back(row);
    }

    void showDocument() const {
        std::cout << "标题: " << title << std::endl;
        std::cout << "正文: " << std::endl;
        std::cout << content << std::endl;
        std::cout << "图片: " << std::endl;
        for (const auto& img : images) {
            std::cout << img << std::endl;
        }
        std::cout << "表格: " << std::endl;
        for (const auto& row : table) {
            for (const auto& cell : row) {
                std::cout << cell << "\t";
            }
            std::cout << std::endl;
        }
    }
};

// 抽象建造者
class DocumentBuilder {
public:
    virtual void buildTitle() = 0;
    virtual void buildContent() = 0;
    virtual void buildImages() = 0;
    virtual void buildTable() = 0;
    virtual std::unique_ptr<Document> getDocument() = 0;
    virtual ~DocumentBuilder() = default;
};

// 具体建造者:HTML文档建造者
class HTMLDocumentBuilder : public DocumentBuilder {
private:
    std::unique_ptr<Document> document;

public:
    HTMLDocumentBuilder() {
        document = std::make_unique<Document>();
    }

    void buildTitle() override {
        document->setTitle("<h1>这是一个HTML文档标题</h1>");
    }

    void buildContent() override {
        document->addContent("<p>这是HTML文档的正文内容。</p>");
    }

    void buildImages() override {
        document->addImage("<img src='image1.jpg' alt='图片1'>");
    }

    void buildTable() override {
        std::vector<std::string> row1 = { "<td>单元格1</td>", "<td>单元格2</td>" };
        std::vector<std::string> row2 = { "<td>单元格3</td>", "<td>单元格4</td>" };
        document->addRowToTable(row1);
        document->addRowToTable(row2);
    }

    std::unique_ptr<Document> getDocument() override {
        return std::move(document);
    }
};

// 具体建造者:Word文档建造者
class WordDocumentBuilder : public DocumentBuilder {
private:
    std::unique_ptr<Document> document;

public:
    WordDocumentBuilder() {
        document = std::make_unique<Document>();
    }

    void buildTitle() override {
        document->setTitle("这是一个Word文档标题(字体、格式等在Word中设置)");
    }

    void buildContent() override {
        document->addContent("这是Word文档的正文内容(格式在Word中设置)");
    }

    void buildImages() override {
        document->addImage("图片路径: image1.jpg(插入到Word中并设置格式)");
    }

    void buildTable() override {
        std::vector<std::string> row1 = { "单元格1", "单元格2" };
        std::vector<std::string> row2 = { "单元格3", "单元格4" };
        document->addRowToTable(row1);
        document->addRowToTable(row2);
    }

    std::unique_ptr<Document> getDocument() override {
        return std::move(document);
    }
};

// 指挥者
class Director {
public:
    std::unique_ptr<Document> construct(DocumentBuilder* builder) {
        builder->buildTitle();
        builder->buildContent();
        builder->buildImages();
        builder->buildTable();
        return builder->getDocument();
    }
};

在上述代码中:

  • Document类表示最终生成的文档,包含标题、正文、图片和表格等成员变量,以及相应的设置和展示方法。
  • DocumentBuilder是抽象建造者,定义了构建文档各个部分的纯虚函数。
  • HTMLDocumentBuilder和WordDocumentBuilder是具体建造者,分别实现了抽象建造者的方法,用于构建 HTML 文档和 Word 文档。
  • Director是指挥者,负责控制文档的构建过程,按照一定顺序调用建造者的方法来构建文档。

4.3 不同格式文档(HTML、Word)的生成测试

下面通过测试代码来展示如何使用建造者模式生成 HTML 和 Word 格式的文档,并对生成的文档进行测试:

cpp 复制代码
int main() {
    Director director;

    // 生成HTML文档
    HTMLDocumentBuilder htmlBuilder;
    std::unique_ptr<Document> htmlDocument = director.construct(&htmlBuilder);
    std::cout << "=== HTML文档 ===" << std::endl;
    htmlDocument->showDocument();

    // 生成Word文档
    WordDocumentBuilder wordBuilder;
    std::unique_ptr<Document> wordDocument = director.construct(&wordBuilder);
    std::cout << "=== Word文档 ===" << std::endl;
    wordDocument->showDocument();

    return 0;
}

在main函数中,首先创建了Director对象,然后分别创建了HTMLDocumentBuilder和WordDocumentBuilder对象,并通过Director的construct方法来构建 HTML 文档和 Word 文档。最后调用showDocument方法展示生成的文档内容。

测试结果分析:

  • 对于 HTML 文档,通过HTMLDocumentBuilder构建的文档内容包含了 HTML 标签,符合 HTML 文档的格式要求,如标题使用<h1>标签,正文使用<p>标签,图片使用<img>标签,表格使用<td>标签等。
  • 对于 Word 文档,由于 Word 文档的格式设置通常在 Word 软件中进行,这里只是简单地在文本中描述了标题、正文、图片路径和表格内容,实际应用中可以使用相关的 Word 文档生成库(如 LibreOfficeKit、Microsoft Word API 等)来将这些内容按照 Word 文档的格式进行生成和排版。

通过上述测试,验证了建造者模式在复杂文档生成系统中的有效性和灵活性,能够根据不同的需求生成不同格式的文档。

相关推荐
奔跑吧邓邓子2 小时前
【C++实战㊵】C++抽象工厂模式:解锁高效对象创建的密钥
c++·实战·抽象工厂模式
jf加菲猫3 小时前
条款11:优先选用删除函数,而非private未定义函数
开发语言·c++
怀旧,4 小时前
【C++】23. C++11(上)
开发语言·c++
小卡皮巴拉5 小时前
【笔试强训】Day1
开发语言·数据结构·c++·算法
初圣魔门首席弟子5 小时前
switch缺少break出现bug
c++·算法·bug
进步青年ccc5 小时前
C++ IO 库全方位解析:从基础到实战
c++·io
博界IT精灵6 小时前
C++入门
c++
泽虞6 小时前
《C++程序设计》笔记p4
linux·开发语言·c++·笔记·算法
什么半岛铁盒6 小时前
C++项目:仿muduo库高并发服务器--------Any类的实现
linux·服务器·数据库·c++·mysql·github