C++设计模式之生成器模式(Builder)

news2024/11/24 3:11:06

文章目录

    • 定义
    • 前言
      • 1. 问题
      • 2. 解决方案
    • 结构
    • 适用场景
    • 实现方法
    • 优点
    • 缺点
    • 与其他模式的关系
    • 实例

定义

生成器是一种创建型设计模式,使你能够分步骤创建复杂对象。该模式允许你使用相同的创建 代码生成不同类型和形式的对象。

前言

1. 问题

假设有这样一个复杂对象,在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中;甚至还有更糟糕的情况,那就是这些代码散落在客户端代码的多个位置。

如果为每种可能的对象都创建一个子类,这可能导致程序过于复杂:

在这里插入图片描述
例如, 我们来思考如何创建一个房屋(House)对象。建造一栋简单的房屋,首先你需要建造四面墙和地板,安装房门和一套窗户,然后再建造一个屋顶。但是如果你想要一栋更宽敞更明亮的房屋,还要有院子和其他设施(例如暖气、排水和供电设备),那又该怎么办呢?

最简单的方法是扩展房屋基类,然后创建一系列涵盖所有参数组合的子类。但最终你将面对相当数量的子类。任何新增的参数(例如门廊类型)都会让这个层次结构更加复杂。

另一种方法则无需生成子类。你可以在房屋“基类”中创建一个包括所有可能参数的超级构造函数,并用它来控制房屋对象。这种方法确实可以避免生成子类,但它却会造成另外一个问题(这些大量的参数不是每次都要全部用上的)。

在这里插入图片描述
通常情况下绝大部分的参数都没有使用,这对于构造函数的调用十分不简洁。例如,只有很少的房子有游泳池,因此与游泳池相关的参数十之八九是毫无用处的。

2. 解决方案

生成器模式建议将对象构造代码从产品类中抽取出来,并将其放在一个名为生成器的独立对象中。

生成器模式能让你分步骤创建复杂对象,生成器不允许其他对象访问正在创建中的产品。
在这里插入图片描述
该模式会将对象构造过程划分为一组步骤, 比如创建墙壁(buildWalls)和创建房门(buildDoor)等。每次创建对象时,你都需要通过生成器对象执行一系列

步骤。重点在于你无需调用所有步骤,而只需调用创建特定对象配置所需的那些步骤即可。

当你需要创建不同形式的产品时,其中的一些构造步骤可能需要不同的实现。例如,木屋的房门可能需要使用木头制造,而城堡的房门则必须使用石头制造。

在这种情况下,你可以创建多个不同的生成器,用不同方式实现一组相同的创建步骤。然后你就可以在创建过程中使用这些生成器(例如按顺序调用多个构造步骤)来生成不同类型的对象。

在这里插入图片描述
例如,假设第一个建造者使用木头和玻璃制造房屋,第二个建造者使用石头和钢铁, 而第三个建造者使用黄金和钻石。在调用同一组步骤后, 第一个建造者会给你一栋普通房屋,第二个会给你一座小城堡,而第三个则会给你一座宫殿。但是,只有在调用构造步骤的客户端代码可以通过通用接口与建造者进行交互时,这样的调用才能返回需要的房屋。

结构

在这里插入图片描述

  1. 生成器(Builder)接口声明在所有类型生成器中通用的产品构造步骤。
  2. 具体生成器(Concrete Builders)提供构造过程的不同实现。具体生成器也可以构造不遵循通用接口的产品。
  3. 产品(Products)是最终生成的对象。由不同生成器构造的产品无需属于同一类层次结构或接口。
  4. 主管(Director)类定义调用构造步骤的顺序,这样你就可以创建和复用特定的产品配置。
  5. 客户端(Client)必须将某个生成器对象与主管类关联。一般情况下,你只需通过主管类构造函数的参数进行一次性关联即可。此后主管类就能使用生成器对象完成后续所有的构造任务。但在客户端将生成器对象传递给主管类制造方法时还有另一种方式。在这种情况下,你在使用主管类生产产品时每次都可以使用不同的生成器。

适用场景

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

假设你的构造函数中有十个可选参数,那么调用该函数会非常不方便;因此,你需要重载这个构造函数,新建几个只有较少参数的简化版。但这些构造函数仍需调用主构造函数,传递一些默认数值来替代省略掉的参数。生成器模式让你可以分步骤生成对象,而且允许你仅使用必须的步骤。应用该模式后,你再也不需要将几十个参数塞进构造函数里了。

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

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

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

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

实现方法

  1. 清晰地定义通用步骤, 确保它们可以制造所有形式的产品。否则你将无法进一步实施该模式。
  2. 在基本生成器接口中声明这些步骤。
  3. 为每个形式的产品创建具体生成器类,并实现其构造步骤。不要忘记实现获取构造结果对象的方法。你不能在生成器接口中声明该方法,因为不同生成器构造的产品可能没有公共接口,因此你就不知道该方法返回的对象类型。但是,如果所有产品都位于单一类层次中,你就可以安全地在基本接口中添加获取生成对象的方法。
  4. 考虑创建主管类。它可以使用同一生成器对象来封装多种构造产品的方式。
  5. 客户端代码会同时创建生成器和主管对象。构造开始前,客户端必须将生成器对象传递给主管对象。通常情况下,客户端只需调用主管类构造函数一次即可。主管类使用生成器对象完成后续所有制造任务。还有另一种方式,那就是客户端可以将生成器对象直接传递给主管类的制造方法。
  6. 只有在所有产品都遵循相同接口的情况下,构造结果可以直接通过主管类获取。否则,客户端应当通过生成器获取构造结果。

优点

● 你可以分步创建对象,暂缓创建步骤或递归运行创建步骤。
● 生成不同形式的产品时,你可以复用相同的制造代码。
● 单一职责原则。你可以将复杂构造代码从产品的业务逻辑中分离出来。

缺点

由于该模式需要新增多个类,因此代码整体复杂程度会有所增加。

与其他模式的关系

● 在许多设计工作的初期都会使用工厂方法(较为简单,而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂、原型或生成器(更灵活但更加复杂)。
● 生成器重点关注如何分步生成复杂对象。抽象工厂专门用于生产一系列相关对象。抽象工厂会马上返回产品,生成器则允许你在获取产品前执行一些额外构造步骤。
● 你可以在创建复杂组合树时使用生成器,因为这可使其构造步骤以递归的方式运行。
● 你可以结合使用生成器和桥接模式: 主管类负责抽象工作,各种不同的生成器负责实现工作。
● 抽象工厂、生成器和原型都可以用单例来实现。

实例

Product.h:

#ifndef  PRODUCT_H_
#define  PRODUCT_H_

#include <string>
#include <iostream>

// 产品类 车
class Car {
 public:
    Car() {}
    void set_car_tire(std::string t) {
        tire_ = t;
        std::cout << "set tire: " << tire_ << std::endl;
    }
    void set_car_steering_wheel(std::string sw) {
        steering_wheel_ = sw;
        std::cout << "set steering wheel: " << steering_wheel_ << std::endl;
    }
    void set_car_engine(std::string e) {
        engine_ = e;
        std::cout << "set engine: " << engine_ << std::endl;
    }

 private:
    std::string tire_;            // 轮胎
    std::string steering_wheel_;  // 方向盘
    std::string engine_;          // 发动机

};

#endif  // PRODUCT_H_

Builder.h:

#ifndef  BUILDER_H_
#define  BUILDER_H_

#include "Product.h"

// 抽象建造者
class CarBuilder {
 public:
    Car getCar() {
        return car_;
    }

    // 抽象方法
    virtual void buildTire() = 0;
    virtual void buildSteeringWheel() = 0;
    virtual void buildEngine() = 0;

 protected:
    Car car_;
};

#endif  // BUILDER_H_

ConcreteBuilder.h:

#ifndef CONCRETE_BUILDER_H_
#define CONCRETE_BUILDER_H_

#include "Builder.h"

// 具体建造者 奔驰
class BenzBuilder : public CarBuilder {
 public:
    // 具体实现方法
    void buildTire() override {
        car_.set_car_tire("benz_tire");
    }
    void buildSteeringWheel() override {
        car_.set_car_steering_wheel("benz_steering_wheel");
    }
    void buildEngine() override {
        car_.set_car_engine("benz_engine");
    }
};

// 具体建造者 奥迪
class AudiBuilder : public CarBuilder {
 public:
    // 具体实现方法
    void buildTire() override {
        car_.set_car_tire("audi_tire");
    }
    void buildSteeringWheel() override {
        car_.set_car_steering_wheel("audi_steering_wheel");
    }
    void buildEngine() override {
        car_.set_car_engine("audi_engine");
    }
};

#endif  // CONCRETE_BUILDER_H_

Director.h:

#ifndef  DIRECTOR_H_
#define  DIRECTOR_H_

#include "Builder.h"

class Director {
 public:
    Director() : builder_(nullptr) {}

    void set_builder(CarBuilder *cb) {
        builder_ = cb;
    }

    // 组装汽车
    Car ConstructCar() {
        builder_->buildTire();
        builder_->buildSteeringWheel();
        builder_->buildEngine();
        return builder_->getCar();
    }

 private:
    CarBuilder* builder_;
};

#endif  // DIRECTOR_H_

main.cpp:

#include "Director.h"
#include "ConcreteBuilder.h"

int main() {
    // 抽象建造者(一般是动态确定的)
    CarBuilder* builder;
    // 指挥者
    Director* director = new Director();
    // 产品
    Car car;

    // 建造奔驰
    std::cout << "==========construct benz car==========" << std::endl;
    builder = new BenzBuilder();
    director->set_builder(builder);
    car = director->ConstructCar();
    delete builder;

    // 建造奥迪
    std::cout << "==========construct audi car==========" << std::endl;
    builder = new AudiBuilder();
    director->set_builder(builder);
    car = director->ConstructCar();
    delete builder;

    std::cout << "==========done==========" << std::endl;
    delete director;
}

编译运行:

$g++ -g main.cpp -o builder -std=c++11
$./builder 
==========construct benz car==========
set tire: benz_tire
set steering wheel: benz_steering_wheel
set engine: benz_engine
==========construct audi car==========
set tire: audi_tire
set steering wheel: audi_steering_wheel
set engine: audi_engine
==========done==========

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/615071.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

100种思维模型之事物关系思维模型-72

具体的事物是形象的、容易观察和理解的&#xff0c;而事物间的关系则容易让人忽视&#xff0c;乃至无法意识到它正在发生作用。 生活中&#xff0c;我们习惯于低估事物关系的影响。 事物关系思维模型一个提醒我们关注事物关系、从宏观层面找到更好的工作方向、实现可持续发展的…

显示反馈与隐式反馈

文章目录 1. 数据分类2. 显性反馈数据模型评价方法&#xff1a;3. 显式反馈转换为隐式反馈4. 隐式反馈的作用5. 参考资料 本文来介绍一下显示反馈与隐式反馈&#xff0c;作为我学习推荐系统的笔记以便日后忘记了可以回过头来温习。 1. 数据分类 显式反馈是指&#xff1a;用户明…

光量子计算机+GPU!NVIDIA、Rolls-Royce和Classiq宣布取得技术突破

​ &#xff08;图片来源&#xff1a;网络&#xff09; 5月21日&#xff0c;英伟达&#xff08;NVIDIA&#xff09;、罗尔斯罗伊斯&#xff08;Rolls-Royce&#xff09;和以色列量子软件公司Classiq宣布了一项量子计算技术突破成果&#xff0c;旨在不断提高喷气发动机的效率。 …

AB32VG:(2)app.cbp工程源码阅读笔记(ADC按键和红外)

文章目录 1.SDK文件目录2.ADCKEY和红外遥控器相关源码2.1 初始化2.2 在定时器中断服务程序中查询按键 3. 更改参数以适应自己的ADC按键板3.1 我的ADC按键板硬件3.2 ADC值转换为键值 4.红外遥控器5. 相关配置 参考文章&#xff1a; 作者&#xff1a;nunu1010&#xff0c;中科蓝…

VSCode+Git+TortoiseGit+Tools

目录 一、Tools 1、VSCode(visual studio code)下载安装 VSCode使用技巧和经验 2、Git下载安装 3、TortoiseGit 简介 3.1、下载安装Git及Tortoisegit 3.2、Tortoisegit拉取gitee仓库到本地 3.3、Git拉取gitee仓库到本地 3.4、Git提交到gitee仓库 4、国内获取GitHub链…

华为OD机试真题B卷 Java 实现【蛇形矩阵】,附详细解题思路

一、题目描述 蛇形矩阵是由1开始的自然数依次排列成的一个矩阵上三角形。 例如&#xff0c;当输入5时&#xff0c;应该输出的三角形为&#xff1a; 1 3 6 10 15 2 5 9 14 4 8 13 7 12 11 二、输入描述 输入正整数N&#xff08;N不大于100&#xff09;。 三、输出描述…

Vector DDFS

矢量数字频率生成器 版本&#xff1a;vivado2021.1 帮助文档&#xff1a;C:/Xilinx/Vivado/2021.1/doc/sysgen/html_help/vectorddfs.html 接口 I 频率控制字&#xff0c;I 输出频率 * 采样时间 * 2^&#xff08;频率分辨率&#xff09;&#xff1b;VI、VO 用于同步&#x…

什么是优雅的代码设计

今天我来解释一下什么样的代码才是优雅的代码设计。当然我们的代码根据实际的应用场景也分了很多维度&#xff0c;有偏向于底层系统的&#xff0c;有偏向于中间件的&#xff0c;也有偏向上层业务的&#xff0c;还有偏向于前端展示的。今天我主要来跟大家分析一下我对于业务代码…

电子科技大学计算机系统结构:课后作业

计算机体系结构作业答案 第一二章作业 1.试述Flynn 分类的4 种计算机系统结构有何特点。 参考答案&#xff1a; Flynn按照指令流和数据流两种不同的组合&#xff0c;把计算机系统的结构分为以下4 类&#xff1a; &#xff08;1&#xff09;单指令流单数据流SISD&#xff0…

这款AI绘画工具也太太太赞了!模型丰富,轻松绘画,赶快收藏起来!

现今科技发展迅速&#xff0c;让人工智能&#xff08;AI&#xff09;成为了我们日常生活中的必备之物。在艺术领域&#xff0c;AI技术也开始广泛应用。特别是AI绘画软件&#xff0c;以其高效、精准的绘画方式&#xff0c;已成为越来越多艺术家、设计师和普通用户绘画的首选工具…

HUSTOJ使用指南

如何快速上手&#xff08;了解系统的功能&#xff09;&#xff1f; admin管理员用户登录&#xff0c;点击右上角管理&#xff0c;仔细阅读管理首页的说明。 切记&#xff1a;题目导入后一次只能删一题&#xff0c;不要导入过多你暂时用不上的题目&#xff0c;正确的方式是每次…

Cron在前端的使用,vue与element ui的vue-cron插件的使用及将定时任务cron表达式解析成中文

文章目录 vue-cron插件的使用安装依赖引用Vue页面去掉秒和年定时任务cron解析成中文该插件存在的一个缺陷 vue-cron插件的使用 安装依赖 执行下面npm命令&#xff1a; npm install vue-cron --save 引用 在想使用cron的vue页面引入以下: import VueCron from ‘vue-cron’ …

node版本管理工具nvm安装和使用

公司的前端项目使用的node版本是10.11.1比较老的版本&#xff0c;但是新开发的项目需要使用vue3viteelectron,需要使用较新的node版本 。综上决定研究研究nvm对node进行切换管理。有相同需求的朋友希望下面的文章可以帮助到你们。借鉴了一些博主的文章&#xff0c;在文章里也总…

重磅!Cloud Ace 在班加罗尔和孟买成立新的据点

Cloud Ace Cooperation&#xff08;总部位于东京千代田区&#xff1b; Makoto Aoki&#xff0c;总裁&#xff09;很高兴地宣布&#xff0c;我们已经在班加罗尔建立了新的开发中心&#xff0c;并在孟买建立了新的销售办事处&#xff0c;作为 Cloud Ace 进一步扩大公司在印度业务…

docker-compose通过volume恢复mysql数据

概述 docker rm是docker删除容器的命令。 会清空容器内的所有数据和配置&#xff0c;即真正的将容器清空并删除。 但是之前通过volume挂载到宿主机上是不受影响的。 docker rm -v如果是-v的命令那么会同时删除通过volume映射到宿主机上的文件 通过volume恢复数据 使用docke…

第六十六天学习记录:《高质量C/C++编程指南》中附录的考试试卷(含答案)

该试卷转载自林锐《高质量C/C编程指南》&#xff0c;先贴下原作者的版权声明。 版权声明&#xff1a;本书的大部分内容取材于作者一年前的书籍手稿&#xff08;尚未出版&#xff09;&#xff0c;现整理汇编成为上海贝尔网络应用事业部的一个规范化文件&#xff0c;同时作为培训…

chatgpt赋能python:Python如何分配内存

Python如何分配内存 Python是一种动态解释型语言&#xff0c;它在运行时分配内存用于存储变量和对象。Python提供了一种内存管理机制&#xff0c;它能够动态地分配和管理内存。本文将介绍Python如何分配内存并讨论与之相关的一些最佳实践。 Python内存管理机制 Python中的所…

Vue.js 中的性能优化是什么?如何进行性能优化?

Vue.js 中的性能优化是什么&#xff1f;如何进行性能优化&#xff1f; Vue.js 是一款流行的前端框架&#xff0c;它具有响应式数据绑定、组件化开发、虚拟 DOM 等特性&#xff0c;使得开发者可以更加高效地构建交互式的用户界面。然而&#xff0c;在实际开发中&#xff0c;由于…

LeetCode 2352. 相等行列对:手动哈希

【LetMeFly】2352.相等行列对&#xff1a;手动哈希 力扣题目链接&#xff1a;https://leetcode.cn/problems/equal-row-and-column-pairs/ 给你一个下标从 0 开始、大小为 n x n 的整数矩阵 grid &#xff0c;返回满足 Ri 行和 Cj 列相等的行列对 (Ri, Cj) 的数目。 如果行和…

ArduPilot之H743遗留配置问题解决

ArduPilot之H743遗留配置问题解决 1. 源由2. 资源3 遗留问题汇总3.1 问题一&#xff1a;无法设置VTX 600m3.2 问题二&#xff1a;双向Dshot未显示RMP转速3.3 问题三&#xff1a;mavlink esp32 2.4G WiFi电传 4. 参考资料 1. 源由 在ArduPilot开源代码之H743BMI270x2ChibiOS配置…