C++ 【生成器模式】

简单介绍

生成器模式属于创建型设计模式 | 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同的对象。(但产品必须要较为复杂且需要详细配置时使用才有意义)

基础理解

Q:为什么使用生成器模式
A:因为当我们需要创建一个较为复杂或需要详细配置的对象(举个例子:造房子)

  • 我们就需要生成许多其他的子类去 配置 房子类。可能包含了许多可能用不上的子类。 并且我们可能会造不同种类不同样式的房子,且造房子的特定顺序也会不一样。那么房子类的构造类就会显得非常不简洁。
  • 所以我们需要对象构造代码从产品类中抽取出来, 并将其放在一个名为生成器的独立对象(builder 抽像类 )中,并且创建不同的生成器以此来满足不同的要求。
    HouseBuilder 具体生成器

Q:生成器模式中的主管类是做什么的?
A:主管只负责按照特定顺序执行生成步骤。其在根据特定步骤或配置来生成产品时会很有帮助。由于客户端可以直接控制生成器,所以严格意义上来说,主管类并不是必需的。

UML 图

符号含义

空心箭头:继承关系
实线箭头:关联关系
虚线箭头:依赖关系
注意Concrete Builder 与 一个实际的产品Product 相关联。Concrete Builder就是用来生成这个Product的。在通过Director 主管类将多个Product 产品以特定的顺序组装完整。
在这里插入图片描述

实现步骤

清晰地定义通用步骤, 确保它们可以制造所有形式的产品。 否则你将无法进一步实施该模式。

在基本生成器接口中声明这些步骤。

为每个形式的产品创建具体生成器类, 并实现其构造步骤。

不要忘记实现获取构造结果对象的方法GetProduct。 你不能在生成器接口 Builder 中声明该方法, 因为不同生成器构造的产品可能没有公共接口, 因此你就不知道该方法返回的对象类型。 但是, 如果所有产品都位于同一类中, 你就可以安全地在基本接口中添加获取生成对象的方法。

考虑创建主管类。 它可以使用同一生成器对象来封装多种构造产品的方式。

客户端代码会同时创建生成器和主管对象。 构造开始前, 客户端必须将生成器对象传递给主管对象。 通常情况下, 客户端只需调用主管类构造函数一次即可。 主管类使用生成器对象完成后续所有制造任务。 还有另一种方式, 那就是客户端可以将生成器对象直接传递给主管类的制造方法。

只有在所有产品都遵循相同接口的情况下, 构造结果可以直接通过主管类获取。 否则, 客户端应当通过生成器获取构造结果。


#include <bits/stdc++.h>
using namespace std;
/**
 * 只有当你的产品相当复杂并且需要广泛配置时,才有意义使用建造者模式。
 *
 * 与其他创建型模式不同,不同的具体建造者可以生成不相关的产品。换句话说,各种建造者的结果可能并不总是遵循相同的接口。
 */

class Product1
{
public:
    std::vector<std::string> parts_;
    void ListParts() const
    {
        std::cout << "产品部件: ";
        for (size_t i = 0; i < parts_.size(); i++)
        {
            if (parts_[i] == parts_.back())
            {
                std::cout << parts_[i];
            }
            else
            {
                std::cout << parts_[i] << ", ";
            }
        }
        std::cout << "\n\n";
    }
};

/**
 * 建造者接口指定了创建产品对象的不同部分的方法。
 */
class Builder
{
public:
    virtual ~Builder() {}
    virtual void ProducePartA() const = 0;
    virtual void ProducePartB() const = 0;
    virtual void ProducePartC() const = 0;
};
/**
 * 具体建造者类遵循建造者接口并提供构建步骤的具体实现。你的程序可能有几种不同的建造者变体,实现方式各不相同。
 */
class ConcreteBuilder1 : public Builder
{
private:
    Product1 *product;

    /**
     * 一个新的构建者实例应该包含一个空白的产品对象,用于进一步组装。
     */
public:
    ConcreteBuilder1()
    {
        this->Reset();
    }

    ~ConcreteBuilder1()
    {
        delete product;
    }

    void Reset()
    {
        this->product = new Product1();
    }
    /**
     * 所有的生产步骤都使用同一个产品实例。
     */

    void ProducePartA() const override
    {
        this->product->parts_.push_back("部件A1");
    }

    void ProducePartB() const override
    {
        this->product->parts_.push_back("部件B1");
    }

    void ProducePartC() const override
    {
        this->product->parts_.push_back("部件C1");
    }

    /**
     * 通常,在将最终结果返回给客户端后,预期会有一个构建者实例准备开始生产另一个产品。这就是为什么在 `getProduct` 方法体末尾调用 reset 方法是一种常见做法。然而,这种行为不是强制性的,
     * 你可以让你的建造者在客户端代码明确调用 reset 之前等待清理前一个结果。
     */

    /**
     * 在这里请注意内存所有权。一旦调用 GetProduct,此函数的用户负责释放此内存。在这里,使用auto_ptr智能指针可能是更好的选择,以避免内存泄漏。
     */

    Product1 *GetProduct()
    {
        Product1 *result = this->product;
        this->Reset();
        return result;
    }
}; /**
    * 主管类仅负责按特定顺序执行建造步骤。当按特定顺序或配置生产产品时,这很有帮助。严格来说,主管类是可选的,因为客户端可以直接控制建造者。
    */
class Director
{
    /**
     * @var Builder
     */
private:
    Builder *builder;
    /**
     * 主管类与客户端代码传递给它的任何建造者实例一起工作。这样,客户端代码可以更改新组装产品的最终类型。
     */

public:
    void set_builder(Builder *builder)
    {
        this->builder = builder;
    }

    /**
     * 主管类可以使用相同的建造步骤构建多种产品变体。
     */

    void BuildMinimalViableProduct()
    {
        this->builder->ProducePartA();
    }

    void BuildFullFeaturedProduct()
    {
        this->builder->ProducePartA();
        this->builder->ProducePartB();
        this->builder->ProducePartC();
    }
};
/**
 * 客户端代码创建一个建造者对象,将其传递给主管,然后启动构建过程。最终结果从建造者对象中检索。
 */
/**
 * 为简单起见,我在这里使用了裸指针,但你可能更喜欢使用智能指针
 */
void ClientCode(Director &director)
{
    ConcreteBuilder1 *builder = new ConcreteBuilder1();
    director.set_builder(builder);
    std::cout << "标准基础产品:\n";
    director.BuildMinimalViableProduct();

    Product1 *p = builder->GetProduct();
    p->ListParts();
    delete p;

    std::cout << "标准完整功能产品:\n";
    director.BuildFullFeaturedProduct();

    p = builder->GetProduct();
    p->ListParts();
    delete p;

    // 请记住,建造者模式可以在没有主管类的情况下使用。
    std::cout << "自定义产品:\n";
    builder->ProducePartA();
    builder->ProducePartC();
    p = builder->GetProduct();
    p->ListParts();
    delete p;

    delete builder;
}

int main()
{
    Director *director = new Director();
    ClientCode(*director);
    delete director;

    system("pause");
    return 0;
}

应用场景

使用生成器模式可避免 “重叠构造函数 (telescoping constructor)” 的出现。

假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 你需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数。也可以让你可以分步骤生成对象,

class Pizza {
    Pizza(int size) { …… }
    Pizza(int size, boolean cheese) { …… }
    Pizza(int size, boolean cheese, boolean pepperoni) { …… }
 }    //只有在 C 或 Java 等支持方法重载的编程语言中才能写出如此复杂的构造函数。

当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用生成器模式。

如果你需要创建的各种形式的产品, 它们的制造过程相似且仅有细节上的差异, 此时可使用生成器模式。
基本生成器接口(Builder )中定义了所有可能的制造步骤, 具体生成器将实现这些步骤来制造特定形式的产品。主管类将负责管理制造步骤的顺序。

使用生成器构造组合树或其他复杂对象。

生成器模式让你能分步骤构造产品。 你可以延迟执行某些步骤而不会影响最终产品。 你甚至可以递归调用这些步骤, 这在创建对象树时非常方便。
生成器在执行制造步骤时, 不能对外发布未完成的产品。 这可以避免客户端代码获取到不完整结果对象的情况。

与其他模式的关系

  • 在许多设计工作的初期都会使用简单工厂模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或[生成器模式] (更灵活但更加复杂)。
  • 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。
  • 你可以结合使用生成器和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。
  • 抽象工厂、 生成器和原型都可以用单例模式来实现。(单例生成器模式的Builder类 抽象工厂的工厂类,原型模式的构造函数:只能创建一个原型。)
  • 你可以在创建复杂组合模式树时使用生成器, 因为这可使其构造步骤以递归的方式运行。## 优缺点
    |优点|缺点 |
    |–|–|
    你可以分步创建对象| 暂缓创建步骤或递归运行创建步骤。
    生成不同形式的产品时你可以复用相同的制造代码。
    单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。
    由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。

生成器模式的浅薄理解,如果有错还望指正。有什么建议也可以留言。谢谢大家。
参考文档

  • 30
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值