当前位置: 首页 > news >正文

实战设计模式之建造者模式

概述

        在实际项目中,我们有时会遇到需要创建复杂对象的情况。这些对象可能包含多个组件或属性,而且每个组件都有自己的配置选项。如果直接使用构造函数或前面介绍的工厂方法来创建这样的对象,可能会导致以下两个严重问题。

        1、参数过多。当一个类有很多可选参数时,构造函数可能会变得非常庞大,难以维护。此外,调用方必须记住哪些参数是必需的,哪些是可选的,这在一定程度上增加了出错的风险。

        2、代码冗余。如果每次创建对象都需要传递大量的参数,那么代码将会变得冗长、复杂且难以理解。即使有些参数有默认值,调用者仍然需要为所有参数提供值。

        相比之下,建造者模式提供了更好的解决方案。它通过引入一个专门的建造者类来逐步构建对象,直到所有的必要部分都被设置完毕。这样不仅可以简化对象的创建过程,还可以提高代码的可读性和可维护性。

        餐厅点餐是运用建造者模式的一个典型例子:菜单上有多种套餐组合,但每个套餐的具体内容可能不同;服务员会根据我们的选择,逐步记录下我们想要的食物和饮料,最后再把这些信息传递给厨房。在这个过程中,服务员扮演了“导演”的角色,而每道菜的厨师则是具体的“建造者”,他们负责按照订单的要求制作食物。

基本原理

        建造者模式的核心思想是:将一个复杂对象的构建过程与其表示分离,从而使得同样的构建过程可以创建不同的表示。它提供了一种分步骤构造复杂对象的方法,避免了大型构造函数带来的问题,并且提高了代码的可读性和可维护性。建造者模式包括如下四个核心组件。

        1、产品类。代表要创建的复杂对象,它可以是一个具体的产品实例,也可以是一系列相关属性的集合。产品类通常包含多个组成部分,每个部分都有自己的配置选项。

        2、抽象建造者。定义了创建产品各个部分的接口,它是所有具体建造者的基类或接口,声明了一系列用于组装产品的抽象方法。这些方法定义了如何构建产品的不同部分,但不涉及具体的实现逻辑。

        3、具体建造者。实现了抽象建造者接口,提供了构建产品的具体实现。每个具体建造者都对应一种特定的产品配置或类型,负责按照预定规则组装出完整的产品。通常情况下,具体建造者还会提供一个类似GetResult的方法来获取最终构建的产品对象。

        4、导演类。导演类负责协调各个具体建造者的执行顺序,它不直接参与产品的构建,而是调用建造者的方法来组织构建过程。它可以根据不同的需求选择合适的建造者,并控制构建的流程。

        基于上面的核心组件,建造者模式的实现主要有以下五个步骤。

        1、定义产品类。创建一个代表最终产品的类,它包含了所有与产品相关的属性。

        2、定义抽象建造者。定义一个接口或抽象类,声明一组用于构建产品的方法。这些方法定义了如何设置产品的各个部分,但不涉及具体的实现细节。

        3、实现具体建造者。根据需要创建多个具体建造者类,每个类都实现了抽象建造者接口。每个具体建造者负责按照特定的规则,组装出完整的产品,并提供GetResult方法来返回最终构建的产品对象。

        4、创建导演类。定义一个导演类,它负责调用具体建造者的方法来组织构建过程。导演类不直接参与产品的构建,而是充当协调者的角色,确保构建过程按照预期进行。

        5、编写应用层代码。在应用层代码中,首先选择合适的具体建造者并将其传递给导演类。导演类会根据预设的构建流程调用建造者的方法,逐步构建出最终的产品对象。最后,应用层可以从建造者那里获取到完全配置好的产品实例。

实战解析

        在下面的实战代码中,我们使用建造者模式模拟了餐厅点餐的应用场景。

        首先,我们定义了产品类COrder。COrder类代表最终的订单对象,它包含了主菜、饮料、甜点以及额外添加的项目。

        然后,我们定义了抽象建造者COrderBuilder。COrderBuilder是一个接口或抽象类,声明了一系列用于设置订单各个部分的方法。每个具体建造者都将实现这些方法,以按照特定规则组装出完整的订单。

        接下来,我们实现了具体建造者CStandardMealBuilder和CVegetarianMealBuilder,分别表示创建标准套餐和素食套餐。每个具体建造者都实现了COrderBuilder接口,并提供了构建特定类型订单的具体逻辑。

        我们还创建了导演类CWaiter,它负责协调各个建造者的执行顺序。它不直接参与订单的构建,而是调用建造者的方法来组织构建过程,这样可以确保构建过程的一致性和灵活性。

        最后,在main函数中,我们首先选择了CStandardMealBuilder来构建一个标准套餐,并通过CWaiter类来指导构建过程。紧接着,我们选择了CVegetarianMealBuilder来构建一个素食套餐。最后,我们打印了两个订单的具体内容,并进行了清理工作。

#include <iostream>
#include <string>
#include <vector>using namespace std;// 定义产品类:订单对象
class COrder
{
public:void SetMainDish(const string& strDish){m_strMainDish = strDish;}void SetDrink(const string& strDrink){m_strDrink = strDrink; }void SetDessert(const string& strDessert){m_strDessert = strDessert;}void AddExtra(const string& strExtra){m_vctExtras.push_back(strExtra);}void Print() const{cout << "Your order contains: " << endl;if (!m_strMainDish.empty()){cout << "Main dish: " << m_strMainDish << endl;}if (!m_strDrink.empty()){cout << "Drink: " << m_strDrink << endl;}if (!m_strDessert.empty()){cout << "Dessert: " << m_strDessert << endl;}if (!m_vctExtras.empty()){cout << "Extras: " << endl;for (const auto& extra : m_vctExtras){cout << "  " << extra << endl;}}}private:string m_strMainDish;               // 主菜string m_strDrink;                  // 饮料string m_strDessert;                // 甜点vector<string> m_vctExtras;         // 额外项
};// 定义抽象建造者
class COrderBuilder
{
public:COrderBuilder(){m_pOrder = new COrder();}virtual ~COrderBuilder() {}virtual void BuildMainDish() = 0;virtual void BuildDrink() = 0;virtual void BuildDessert() = 0;virtual void AddExtras() = 0;virtual COrder* GetResult(){return m_pOrder; }protected:COrder* m_pOrder;
};// 实现具体建造者:标准套餐建造者
class CStandardMealBuilder : public COrderBuilder
{
public:void BuildMainDish() override{m_pOrder->SetMainDish("Roast Chicken");}void BuildDrink() override{m_pOrder->SetDrink("Cola");}void BuildDessert() override{m_pOrder->SetDessert("Ice Cream");}void AddExtras() override{m_pOrder->AddExtra("Potato Fries");}
};// 实现具体建造者:素食套餐建造者
class CVegetarianMealBuilder : public COrderBuilder
{
public:void BuildMainDish() override{m_pOrder->SetMainDish("Vegetable Curry");}void BuildDrink() override{m_pOrder->SetDrink("Lemonade");}void BuildDessert() override{m_pOrder->SetDessert("Fruit Salad");}void AddExtras() override{m_pOrder->AddExtra("Breadsticks");}
};// 创建导演类:服务员
class CWaiter
{
public:void Construct(COrderBuilder* pBuilder){pBuilder->BuildMainDish();pBuilder->BuildDrink();pBuilder->BuildDessert();pBuilder->AddExtras();}
};int main()
{CWaiter waiter;// 选择标准套餐COrderBuilder* pBuilder = new CStandardMealBuilder();waiter.Construct(pBuilder);COrder* pStandardOrder = pBuilder->GetResult();cout << "***** Standard Meal *****" << endl;pStandardOrder->Print();cout << endl;delete pBuilder;// 选择素食套餐pBuilder = new CVegetarianMealBuilder();waiter.Construct(pBuilder);COrder* pVegetarianOrder = pBuilder->GetResult();cout << "***** Vegetarian Meal *****" << endl;pVegetarianOrder->Print();delete pBuilder;// 清理delete pStandardOrder;delete pVegetarianOrder;return 0;
}

总结

        对于那些需要很多参数的对象创建,尤其是这些参数大多数是可选的时候,使用建造者模式可以让代码更加易读、易于维护。相比长串的构造函数参数列表,建造者模式提供了更自然的接口来逐步设定属性。

        但建造者模式也引入了额外的类(即具体的建造者),因此会稍微增加系统的复杂度和整体大小。对于简单的对象创建,这种模式可能显得过于繁琐。如果要添加新的产品,就需要修改现有的建造者接口或创建新的建造者子类,这可能会影响现有代码的稳定性。另外,如果建造者负责多个不同类型的对象创建,可能会导致建造者类变得臃肿,承担过多责任,从而违背了单一职责原则。

相关文章:

实战设计模式之建造者模式

概述 在实际项目中&#xff0c;我们有时会遇到需要创建复杂对象的情况。这些对象可能包含多个组件或属性&#xff0c;而且每个组件都有自己的配置选项。如果直接使用构造函数或前面介绍的工厂方法来创建这样的对象&#xff0c;可能会导致以下两个严重问题。 1、参数过多。当一个…...

活动预告 | Microsoft Azure 在线技术公开课:使用 Azure OpenAI 服务构建生成式应用

课程介绍 通过 Microsoft Learn 免费参加 Microsoft Azure 在线技术公开课&#xff0c;掌握创造新机遇所需的技能&#xff0c;加快对 Microsoft Cloud 技术的了解。参加我们举办的“使用 Azure OpenAI 服务构建生成式应用”活动&#xff0c;了解如何使用包括 GPT 在内的强大的…...

ubuntu安装firefox

firefox下载地址&#xff1a;https://ftp.mozilla.org/pub/firefox/releases/ 卸载 sudo apt-get update dpkg --get-selections |grep firefox apt-get purge firefox 解压 tar -xjf firefox*.tar.bz2复制文件 sudo mv firefox/ /opt/firefox30sudo mv /usr/bin/firefox /…...

计算机网络原理(谢希仁第八版)第4章课后习题答案

第四章 网络层 详细计算机网络&#xff08;谢希仁-第八版&#xff09;第四章习题全解_计算机网络第八版谢希仁课后答案-CSDN博客 1.网络层向上提供的服务有哪两种&#xff1f;是比较其优缺点。网络层向运输层提供 “面向连接”虚电路&#xff08;Virtual Circuit&#xff09;服…...

RabbitMQ-基本使用

RabbitMQ: One broker to queue them all | RabbitMQ 官方 安装到Docker中 docker run \-e RABBITMQ_DEFAULT_USERrabbit \-e RABBITMQ_DEFAULT_PASSrabbit \-v mq-plugins:/plugins \--name mq \--hostname mq \-p 15672:15672 \-p 5672:5672 \--network mynet\-d \rabbitmq:3…...

从零开始学架构——互联网架构的演进

1 技术演进 1.1 技术演进的动力 对于新技术&#xff0c;我们应该站在行业的角度上思考&#xff0c;哪些技术我们要采取&#xff0c;哪些技术我们不能用&#xff0c;投入成本过大会不会导致满盘皆输&#xff1f;市场、技术、管理三者组成的业务发展铁三角&#xff0c;任何一个…...

python +tkinter绘制彩虹和云朵

python tkinter绘制彩虹和云朵 彩虹&#xff0c;简称虹&#xff0c;是气象中的一种光学现象&#xff0c;当太阳光照射到半空中的水滴&#xff0c;光线被折射及反射&#xff0c;在天空上形成拱形的七彩光谱&#xff0c;由外圈至内圈呈红、橙、黄、绿、蓝、靛、紫七种颜色。事实…...

重新整理机器学习和神经网络框架

本篇重新梳理了人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&#xff09;、神经网络&#xff08;NN&#xff09;和深度学习&#xff08;DL&#xff09;之间存在一定的包含关系&#xff0c;以下是它们的关系及各自内容,以及人工智能领域中深度学习分支对比整理。…...

TypyScript从入门到精通

TypyScript从入门到精通 TypyScript 是什么&#xff1f;增加了什么环境搭建二、为何需要 TypeScript三、编译 TypeScript四、类型声明五、类型推断基本类型六、类型总览JavaScript 中的数据类型TypeScript 中的数据类型1. 上述所有 JavaScript 类型2. 六个新类型&#xff1a;3.…...

【MATLAB】绘制投资组合的有效前沿

文章目录 一、数据准备二、有效前沿三、代码3.1 数据批量读取、预处理3.2 绘制可行集3.3 绘制有效前沿3.4 其它-最大夏普率 一、数据准备 准备多个股票的的历史数据&#xff0c;目的就是找到最优的投资组合。 下载几个标普500里面的公式的股票数据吧&#xff0c;下载方法也可…...

matlab时频分析库

time frequency gallery...

GBase 8s 数据库备份还原

每一天都是一个新的篇章&#xff0c;等待着你去书写属于自己的故事&#xff01;&#xff01;&#xff01; 一&#xff1a;备份 1.1.下载脚本文件&#xff0c;并上传到数据库服务器上相应目录。 解压后目录为&#xff1a; 说明&#xff1a; dbcomm.sh&#xff1a;导出注释脚本…...

C++模板相关概念汇总

文章目录 一、模板的概念与作用二、函数模板模板的非类型参数调用顺序 三、类模板四、模板的编译模型 一、模板的概念与作用 C模板是一种强大的代码复用机制&#xff0c;它允许程序员编写通用的代码&#xff0c;能够处理不同类型的数据&#xff0c;而无需为每种类型都重复编写…...

MYSQL------sql基础

SQL基础与简介 定义&#xff1a;SQL即结构化查询语言&#xff08;Structured Query Language&#xff09;&#xff0c;是一种特殊目的的编程语言&#xff0c;用于存取数据以及查询、更新和管理关系数据库系统。作用&#xff1a;可以用于数据库的创建、数据的插入、查询、更新和…...

React Router 用法概览

React Router 用法 React 使得开发者能够轻松地创建交互式的单页应用&#xff08;SPA&#xff09;&#xff0c;单页应用的一个常见挑战是如何处理页面导航和路由吗&#xff0c;React Router 就是解决这个问题的工具 路由&#xff08;Router&#xff09;是 React Router 的核心…...

网络安全之高防IP的实时监控精准防护

高防IP是一种网络安全设备&#xff0c;用于保护网络服务不受到各类攻击的影响&#xff0c;确保业务的持续稳定运行。它通过监控、识别和封锁恶意攻击流量&#xff0c;提供高级别的防护&#xff0c;降低业务被攻击的风险&#xff0c;并提升网络的稳定性和可靠性。 一、实时监控的…...

2024年中国新能源汽车用车发展怎么样 PaperGPT(二)

用车趋势深入分析 接上文&#xff0c;2024年中国新能源汽车用车发展怎么样 PaperGPT&#xff08;一&#xff09;-CSDN博客本文将继续深入探讨新能源汽车的用车强度、充电行为以及充电设施的现状。 用车强度 月均行驶里程&#xff1a;2024年纯电车辆月均行驶超过1500公里&…...

LINUXC 时间相关操作

文章目录 时间戳获取本地时间struct tm 结构体高精度的时间struct timeval 结构体相关函数time()localtime()gmtime()gettimeofday()strftime()mktime() 示例代码 时间戳 时间戳是指计算机中存储的数字型时间。它是以一个特定的时间点作为起点&#xff08;通常是1970年1月1日0…...

网络游戏之害

网络游戏之害&#xff1a; 网络游戏于今之世风靡四方&#xff0c;其娱人耳目、畅人心怀之效&#xff0c;固为人知&#xff0c;然所藏之害&#xff0c;若隐伏之暗潮&#xff0c;汹涌而至时&#xff0c;足以覆舟&#xff0c;尤以青年为甚&#xff0c;今且缕析其害&#xff0c;以…...

SpringMVC的消息转换器

SpringMVC的消息转换器&#xff08;Message Converter&#xff09;是Spring框架中用于处理HTTP请求和响应体与Java对象之间转换的组件。它们使得开发人员可以轻松地将HTTP请求的数据映射到方法参数&#xff0c;并将返回的对象转换为HTTP响应。 工作原理 当一个HTTP请求到达Spr…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

JVM 内存结构 详解

内存结构 运行时数据区&#xff1a; Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器&#xff1a; ​ 线程私有&#xff0c;程序控制流的指示器&#xff0c;分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 ​ 每个线程都有一个程序计数…...

MFC 抛体运动模拟:常见问题解决与界面美化

在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器

一、原理介绍 传统滑模观测器采用如下结构&#xff1a; 传统SMO中LPF会带来相位延迟和幅值衰减&#xff0c;并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF)&#xff0c;可以去除高次谐波&#xff0c;并且不用相位补偿就可以获得一个误差较小的转子位…...

Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么&#xff1f;它的作用是什么&#xff1f; Spring框架的核心容器是IoC&#xff08;控制反转&#xff09;容器。它的主要作用是管理对…...