设计模式——建造者设计模式(创建型)

article/2025/6/22 23:34:48

摘要

本文详细介绍了建造者设计模式,这是一种创建型设计模式,旨在将复杂对象的构建过程与其表示分离,便于创建不同表示。文中阐述了其设计意图,如隐藏创建细节、提升代码可读性和可维护性,并通过构建电脑的示例加以说明。接着展示了建造者模式的结构,包括抽象建造者、具体建造者、指挥者和产品角色。还提供了 Java 实现示例,分析了其特点与好处,探讨了适用场景,包括适合与不适合的情况,并通过风控请求类的实战示例展示了其优势。最后,提出了结合责任链与建造者构建不同风险场景组合的思考方向。

1. 建造者设计模式定义

建造者模式是一种创建型设计模式,用于将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。定义总结:将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的对象。

1.1. 🧠 设计意图:

  • 屏蔽对象创建的细节,客户端不需要知道构建步骤。
  • 提高代码的可读性、可维护性。
  • 不同建造者构建不同产品,便于扩展。

1.2. 📝 举例说明:

假设我们要构建一台电脑,电脑由 CPU、内存、硬盘等多个部分组成,如果不使用建造者模式,每次创建电脑都要手动写出完整流程,容易出错。使用建造者模式之后,我们可以:

  • 定义不同的建造者(如 GamingComputerBuilderOfficeComputerBuilder)。
  • 使用统一的构建流程(由 Director 控制)生成对应配置的电脑对象。

2. 建造者设计模式结构

2.1. 建造者设计模式类图

建造者模式包含如下角色:

  • Builder:抽象建造者
  • ConcreteBuilder:具体建造者
  • Director:指挥者
  • Product:产品角色

2.2. 建造者设计模式时序图

3. 建造者设计模式实现方式

3.1. ✅ Java 实现示例(以构建一台电脑为例)

3.1.1. 产品类(Product)

public class Computer {private String cpu;private String memory;private String storage;// setterpublic void setCpu(String cpu) { this.cpu = cpu; }public void setMemory(String memory) { this.memory = memory; }public void setStorage(String storage) { this.storage = storage; }@Overridepublic String toString() {return "Computer [CPU=" + cpu + ", Memory=" + memory + ", Storage=" + storage + "]";}
}

3.1.2. 抽象建造者(Builder)

public interface ComputerBuilder {void buildCpu();void buildMemory();void buildStorage();Computer getComputer();
}

3.1.3. 具体建造者(ConcreteBuilder)

public class GamingComputerBuilder implements ComputerBuilder {private Computer computer = new Computer();@Overridepublic void buildCpu() {computer.setCpu("Intel i9");}@Overridepublic void buildMemory() {computer.setMemory("32GB DDR5");}@Overridepublic void buildStorage() {computer.setStorage("2TB SSD");}@Overridepublic Computer getComputer() {return computer;}
}

3.1.4. 指挥者(Director)

public class ComputerDirector {public Computer construct(ComputerBuilder builder) {builder.buildCpu();builder.buildMemory();builder.buildStorage();return builder.getComputer();}
}

3.1.5. 客户端(Client)

public class Main {public static void main(String[] args) {ComputerBuilder builder = new GamingComputerBuilder();ComputerDirector director = new ComputerDirector();Computer computer = director.construct(builder);System.out.println(computer);}
}

3.2. 🧠 建造者设计模式特点与好处

  • 将构建过程与表示解耦,提高灵活性
  • 更适合构建复杂对象或多步骤构建的对象
  • 客户端无需关心细节,适配不同场景(如游戏电脑、办公电脑)

4. 建造者设计模式适合场景

4.1. ✅ 建造者设计模式适合的场景

场景描述

举例说明

对象构建复杂,包含多个可选或必须参数、或有构建顺序要求

如一台电脑需设置 CPU、内存、硬盘等,并按照一定顺序构造

同一个构建过程,生成不同表现形式的对象

如根据配置构建不同风格的 UI 界面,或生成不同类型的报表(PDF/HTML/Excel)

希望封装构建过程,将构建与表示分离,构建逻辑复杂

如报文协议构建、SQL 语句构建、复杂 HTTP 请求构建等

需要一步一步构建对象,各步骤可自由组合/复用

如订单生成流程、商品 SKU 属性配置流程等

希望在创建对象时不直接暴露其创建逻辑,而是通过统一接口进行构建

Spring 中的 RestTemplateBuilderHttpClientBuilder

4.2. ❌ 不适合使用建造者模式的场景

场景描述

原因说明

1️⃣ 对象结构简单,参数少或构建过程简单

使用构造函数或静态工厂方法即可,无需引入复杂建造者类

2️⃣ 对象变化频繁,构建逻辑变化快且不可复用

每次变化都要修改建造者结构,维护成本高

3️⃣ 需要一次性创建大量简单对象

建造者模式对象构建慢,效率低,适合复杂对象

4️⃣ 创建对象不涉及多个步骤或步骤之间没有顺序依赖

可以直接使用构造器重载或 @Builder注解

4.3. ✅ 示例对比

4.3.1. ✔ 适合建造者模式的示例

User user = new UserBuilder().setName("张三").setAge(30).setEmail("zhangsan@example.com").build();

4.3.2. ❌ 不适合建造者模式的示例(对象简单)

User user = new User("张三", 30); // 直接构造函数更简单

5. 建造者设计模式实战示例

以下是一个 建造者设计模式在 Spring 项目中用于金融风控场景 的实战示例,场景描述为:构建一个复杂的风控请求对象 RiskRequest,该对象包含多个字段、可能依赖外部配置、构建顺序有要求,并且需要灵活组合。

5.1. 🌟 场景背景

在金融风控系统中,需要对接多个风控策略服务,而每次调用前都需构造复杂的风控请求体 RiskRequest,其字段包括用户、交易、设备、地理位置、行为历史等,字段较多、部分字段可选、构建过程复杂。

5.2. 🧱 建造者模式结构设计

5.2.1. 风控请求类 RiskRequest

public class RiskRequest {private String userId;private String ip;private String deviceId;private String location;private BigDecimal amount;private String channel;private Map<String, Object> extension;// 私有构造器,避免外部直接 newprivate RiskRequest() {}// Getter 省略public static class Builder {private final RiskRequest request = new RiskRequest();public Builder userId(String userId) {request.userId = userId;return this;}public Builder ip(String ip) {request.ip = ip;return this;}public Builder deviceId(String deviceId) {request.deviceId = deviceId;return this;}public Builder location(String location) {request.location = location;return this;}public Builder amount(BigDecimal amount) {request.amount = amount;return this;}public Builder channel(String channel) {request.channel = channel;return this;}public Builder extension(Map<String, Object> ext) {request.extension = ext;return this;}public RiskRequest build() {// 可添加参数校验逻辑return request;}}
}

5.2.2. Spring 风控服务中使用

@Service
public class RiskEngineService {public RiskResult assessRisk(TransactionContext ctx) {RiskRequest request = new RiskRequest.Builder().userId(ctx.getUserId()).ip(ctx.getIp()).deviceId(ctx.getDeviceId()).location(ctx.getLocation()).amount(ctx.getAmount()).channel("APP").extension(Map.of("loginTime", ctx.getLoginTime())).build();// 调用远程风控引擎return remoteRiskClient.invoke(request);}
}

5.3. ✅ 优势分析

维度

表现

可读性

.xxx().xxx()

链式构建清晰易读

可维护性

字段变更不影响业务逻辑代码,只需改 builder

可扩展性

可灵活扩展参数、做默认值处理

解耦

业务逻辑和构建逻辑分离,保持 Service 层简洁

5.4. 🔄 延伸:结合 Lombok 简化构建(若字段不太复杂)

@Data
@Builder
public class RiskRequest {private String userId;private String ip;private String deviceId;private String location;private BigDecimal amount;private String channel;private Map<String, Object> extension;
}

然后直接用:

RiskRequest request = RiskRequest.builder().userId("u123").ip("192.168.1.1").amount(new BigDecimal("999.00")).build();

6. 建造者设计模式思考

6.1. 结合责任链+建造者构建不同风险场景组合?

结合 责任链(Chain of Responsibility)+ 建造者模式(Builder Pattern),可以优雅地解决金融风控场景中 构建复杂请求对象时的多变逻辑组合问题

6.1.1. 📌 背景场景

你在构建 RiskRequest 时,可能存在如下 变化点

  • 不同业务线(信贷、支付、转账)关注字段不同;
  • 不同渠道(APP、小程序、Web)获取数据来源不同;
  • 某些字段需要动态计算(如 IP 解析地理位置);
  • 某些字段需要兜底或配置中心取默认值。

这些都很适合通过责任链机制 按模块职责划分建造逻辑,结合建造者模式,按步骤组合构建一个完整对象。

6.1.2. 🧱 设计结构图

RiskRequestBuilder├── RiskBuildHandlerA(构建用户信息)├── RiskBuildHandlerB(构建设备信息)├── RiskBuildHandlerC(构建交易信息)├── RiskBuildHandlerD(构建位置信息)└── RiskBuildHandlerX(扩展字段...)

6.1.3. ✅ 风控请求实体 + Builder

public class RiskRequest {private String userId;private String ip;private String location;private BigDecimal amount;private Map<String, Object> ext;private RiskRequest() {}public static class Builder {private final RiskRequest request = new RiskRequest();public Builder userId(String userId) {request.userId = userId; return this;}public Builder ip(String ip) {request.ip = ip; return this;}public Builder location(String location) {request.location = location; return this;}public Builder amount(BigDecimal amount) {request.amount = amount; return this;}public Builder ext(Map<String, Object> ext) {request.ext = ext; return this;}public RiskRequest build() {return request;}}
}

6.1.4. ✅ 定义责任链接口

public interface RiskRequestBuilderHandler {void handle(RiskRequest.Builder builder, RiskContext context);
}

6.1.5. ✅ 各构建器实现类(每个负责构建一部分)

@Component
public class UserInfoBuilderHandler implements RiskRequestBuilderHandler {public void handle(RiskRequest.Builder builder, RiskContext context) {builder.userId(context.getUserId());}
}@Component
public class IpInfoBuilderHandler implements RiskRequestBuilderHandler {public void handle(RiskRequest.Builder builder, RiskContext context) {builder.ip(context.getIp());}
}@Component
public class LocationBuilderHandler implements RiskRequestBuilderHandler {public void handle(RiskRequest.Builder builder, RiskContext context) {// 可通过 IP 反查地址builder.location("Shanghai"); // 示例}
}

6.1.6. ✅ 构建入口类(责任链容器)

@Component
public class RiskRequestDirector {@Autowiredprivate List<RiskRequestBuilderHandler> handlers;public RiskRequest build(RiskContext context) {RiskRequest.Builder builder = new RiskRequest.Builder();for (RiskRequestBuilderHandler handler : handlers) {handler.handle(builder, context);}return builder.build();}
}

✅ Spring 会自动注入所有 RiskRequestBuilderHandler,实现责任链顺序处理(可用 @Order 控制顺序)

6.1.7. ✅ 风控服务中使用

@Service
public class RiskService {@Autowiredprivate RiskRequestDirector requestDirector;public RiskResult runRisk(TransactionContext ctx) {RiskContext riskContext = convert(ctx);RiskRequest request = requestDirector.build(riskContext);return remoteRiskClient.invoke(request);}
}

6.2. 🌟 责任链+建造者总结

描述

解耦构建逻辑

每个字段构建逻辑拆分为独立模块

符合开闭原则

新增构建字段不影响已有代码

动态适配

可结合配置中心动态选择构建器链

更适合扩展

支持多业务场景、策略组装、Mock 等

6.3. 🔧 可扩展方向

  • 支持责任链动态配置(按业务线、渠道等匹配);
  • 风控字段来源插件化(支持 SPI 加载数据构建器);
  • 构建失败字段日志审计(异常处理链);
  • 抽象为风控建模平台中的组件。

博文参考

  • 建造者设计模式(生成器模式)
  • 创建型 - 生成器(Builder) | Java 全栈知识体系
  • 4. 建造者模式 — Graphic Design Patterns

http://www.hkcw.cn/article/wYFfyuxDqg.shtml

相关文章

深入Java性能调优:原理详解与实战

一、JVM内存模型与GC机制 原理&#xff1a; 堆内存结构&#xff1a; 新生代&#xff1a;Eden 2个Survivor区&#xff08;Minor GC&#xff09; 老年代&#xff1a;长期存活对象&#xff08;Major GC/Full GC&#xff09; 元空间&#xff1a;类元信息&#xff08;替代永久代…

acwing刷题

目录 6122. 农夫约翰的奶酪块 6123. 哞叫时间 6122. 农夫约翰的奶酪块 #include <iostream> using namespace std; int res; int n, q; int X[1010][1010]; int Y[1010][1010]; int Z[1010][1010]; void solve() {int x, y, z;cin >> x >> y >> z;X…

姜老师的MBTI课程:MBTI是可以转变的

我们先来看内向和外向这条轴&#xff0c;I和E内向和外向受先天遗传因素的影响还是比较大的&#xff0c;因为它事关到了你的硬件&#xff0c;也就是大脑的模型。但是我们在大五人格的排雷避坑和这套课程里面都强调了一个观点&#xff0c;内向和外向各有优势&#xff0c;也各有不…

leetcode hot100刷题日记——34.将有序数组转换为二叉搜索树

First Blood&#xff1a;什么是平衡二叉搜索树&#xff1f; 二叉搜索树&#xff08;BST&#xff09;的性质 左小右大&#xff1a;每个节点的左子树中所有节点的值都小于该节点的值&#xff0c;右子树中所有节点的值都大于该节点的值。 子树也是BST&#xff1a;左子树和右子树也…

使用yocto搭建qemuarm64环境

环境 yocto下载 # 源码下载 git clone git://git.yoctoproject.org/poky git reset --hard b223b6d533a6d617134c1c5bec8ed31657dd1268 构建 # 编译镜像 export MACHINE"qemuarm64" . oe-init-build-env bitbake core-image-full-cmdline 运行 # 跑虚拟机 export …

探索TiDB数据库:WordPress在分布式数据库上的部署实践

作者&#xff1a; 江湖有缘 原文来源&#xff1a; https://tidb.net/blog/359d4e00 引言 在当今数据驱动的互联网应用中&#xff0c;数据库的性能与可扩展性已成为系统架构中的关键一环。WordPress 作为全球最流行的网站内容管理系统之一&#xff0c;传统上依赖于 MySQL 等…

2.3JS变量和数据类型m

1.认识JS变量 变化数据的记录--变量 2.变量的命名格式 在JS中如何命名一个变量呢 变量的声明&#xff1a;在JS中声明一个变量使用var关键字&#xff08;variable单词的缩写&#xff09;&#xff08;后续学习ES6还有let、const声明方式&#xff09; 变量赋值&#xff1a;使用给变…

深度学习总结(41)

微调预训练模型 另一种常用的模型复用方法是微调&#xff0c;如图所示&#xff0c;它与特征提取互为补充。微调是指&#xff0c;对于用于特征提取的已冻结模型基&#xff0c;将其顶部几层“解冻”​&#xff0c;并对这解冻的几层与新增加的部分&#xff08;本例中为全连接分类…

QT入门学习

一: 新建QT项目 二:QT文件构成 2.1 first.pro 项目管理文件&#xff0c;下面来看代码解析 QT core guigreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11TARGET main# The following define makes your compiler emit warnings if you use # any Qt feature …

kaggle 预测房价

利用简单的线性模型&#xff0c;训练kaggle 房屋数据集&#xff1a; import os import random import tarfile import time import zipfile import pandas as pd import requests import torch from torch import nn from torch.utils import data from matplotlib import pyp…

ASP.NET Core SignalR的基本使用

文章目录 前言一、SignalR是什么&#xff1f;在 ASP.NET Core 中的关键特性&#xff1a;SignalR 工作原理简图&#xff1a; 二、使用步骤1.创建ASP.NET Core web Api 项目2.添加 SignalR 包3.创建 SignalR Hub4.配置服务与中间件5.创建控制器(模拟服务器向客户端发送消息)6.创建…

AI书签管理工具开发全记录(七):页面编写与接口对接

文章目录 AI书签管理工具开发全记录&#xff08;七&#xff09;&#xff1a;页面编写与接口对接前言 &#x1f4dd;1. 页面功能规划 &#x1f4cc;2. 接口api编写 &#x1f4e1;2.1 创建.env,设置环境变量2.2 增加axios拦截器2.3 创建接口 2. 页面编写 &#x1f4c4;2.1 示例代…

“AI 编程三国杀” Google Jules, OpenAl Codex, Claude Code,人类开始沦为AI编程发展的瓶颈?

AI 编程三国杀:Google Jules, OpenAI Codex, Claude code “AI 编程三国杀”是一个形象的比喻,借指当前 AI 编程领域中几个主要参与者之间的激烈竞争与并存的局面。这其中,Google、OpenAI 以及 Anthropic (Claude 的开发者) 是重要的“国家”,而它们各自的 AI 编程工具则是…

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 文件事件处理部分)

分析客户端和服务端网络诵信交互实现 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 命令请求的执行过程案例分析介绍发送命令请求读取命令请求客户端状态的argv属性和argc属性命令执行器第…

第29次CCF计算机软件能力认证-3-LDAP

LDAP 刷新 时间限制&#xff1a; 10.0 秒 空间限制&#xff1a; 512 MiB 下载题目目录&#xff08;样例文件&#xff09; 题目背景 西西艾弗岛运营公司是一家负责维护和运营岛上基础设施的大型企业&#xff0c;拥有数千名员工。公司内有很多 IT 系统。 为了能够实现这些…

2025年- H63-Lc171--33.搜索旋转排序数组(2次二分查找,需二刷)--Java版

1.题目描述 2.思路 输入&#xff1a;旋转后的数组 nums&#xff0c;和一个整数 target 输出&#xff1a;target 在 nums 中的下标&#xff0c;如果不存在&#xff0c;返回 -1 限制&#xff1a;时间复杂度为 O(log n)&#xff0c;所以不能用遍历&#xff0c;必须使用 二分查找…

HomeKit 基本理解

概括 HomeKit 将用户的家庭自动化信息存储在数据库中&#xff0c;该数据库由苹果的内置iOS家庭应用程序、支持HomeKit的应用程序和其他开发人员的应用程序共享。所有这些应用程序都使用HomeKit框架作为对等程序访问数据库. Home 只是相当于 HomeKit 的表现层,其他应用在实现 …

秒杀系统—5.第二版升级优化的技术文档三

大纲 8.秒杀系统的秒杀库存服务实现 9.秒杀系统的秒杀抢购服务实现 10.秒杀系统的秒杀下单服务实现 11.秒杀系统的页面渲染服务实现 12.秒杀系统的页面发布服务实现 8.秒杀系统的秒杀库存服务实现 (1)秒杀商品的库存在Redis中的结构 (2)库存分片并同步到Redis的实现 (3…

尚硅谷-尚庭公寓知识点

文章目录 尚庭公寓知识点1、转换器(Converter)2、全局异常3、定时任务1. 核心步骤(1) 启用定时任务(2) 创建定时任务 2. Scheduled 参数详解3. Cron 表达式语法4. 配置线程池&#xff08;避免阻塞&#xff09;5. 动态控制任务&#xff08;高级用法&#xff09;6. 注意事项 4、M…

字符串~~~

字符串~~ KMP例题1.无线传输2.删除字符串3.二叉树中的链表 AC自动机Manacher例题 扩展KMP字符串哈希 KMP &#xff08;1&#xff09; &#xff08;2&#xff09; &#xff08;3&#xff09; 经典例题 https://leetcode.cn/problems/find-the-index-of-the-first-occurre…