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

【设计模式】使用建造者模式组装对象并加入自定义校验

文章目录

  • 1.前言
    • 1.1.创建对象时的痛点
  • 2.建造者模式
    • 2.1 被建造类准备
    • 2.2.建造者类实现
    • 2.3.构建对象测试
    • 2.4.使用lombok简化建造者
    • 2.5.lombok简化建造者的缺陷
  • 3.总结

1.前言

在我刚入行不久的时候就听说过建造者模式这种设计模式,当时只知道是用来组装对象,使用起来和先new出对象再一个一个的set字段值也差不多,没有去深究这种设计模式。后来随着在业务中写的代码越来越多,同时也去查阅了一些资料,慢慢的对这两者的区别有了一点理解。于是对为什么要用建造者模式,以及相对于直接set的优劣势做一个简单的梳理。

1.1.创建对象时的痛点

在不同的业务流程需求当中,在创建了对象之后对于对象各个字段的赋值可能还会有一些额外的要求,这里举几个典型的例子:

  • 对象中的某几个字段不能为空,或需要满足一定的校验要求。
  • 字段与字段之间有联动,例如:填写了电话号码就不用填邮箱地址,但两者不能都为空
  • 某些字段值只能创建时赋值一次,赋值后就不可变了

对于上面的这些需求,直接通过set不是太好实现的,可以有这样一些思路:

  • 在类的构造函数中写上校验、联动等逻辑
  • 借助一下其他的方法,比如写一个静态工具方法,传入参数生成对象并返回

但是问题又来了,假如现在有好几个业务流程,每个流程当中需要set不同的字段值,那就需要我们 针对每个不同的需求都创建对应的方法(或构造函数) 来生成不同的对象。
当这么做的时候,后续的迭代拓展会让方法越来越多,拓展变动越来越困难。


针对对象的字段组装需求上的痛点,可以使用建造者模式来处理,功能疑更易维护且更加内聚。

2.建造者模式

接下来就用建造者模式实现一下上面的需求,假设我们现在有一个Person类型,包含了name,age,phone,email字段,其中:

  • name和age不能为空
  • phone和email不能同时为空
  • 所有的属性在创建时初始化,创建完成后不能再修改

2.1 被建造类准备

我们先创建一个Person类,并且只提供get方法,满足不能修改的需求,必要时还可以给每个字段加上final修饰:

import lombok.Getter;@Getter
public class PersonModel {private String name;private Integer age;private String phone;private String email;
}

接下来就需要提供一个构造函数用于完成初始化工作,这个构造函数接收一个Builder对象作为参数,这个对象就是我们接下来要说的建造者对象。

import lombok.Getter;@Getter
public class PersonModel {private String name;private Integer age;private String phone;private String email;// 用于初始化的构造函数public PersonModel(Builder builder) {this.name = builder.name;this.age = builder.age;this.phone = builder.phone;this.email = builder.email;}
}

2.2.建造者类实现

建造者类拥有与被建造的类相同的属性,同时给每个属性提供一个同名方法,这个方法用于给字段赋值,并且会返回建造者对象(方便链式调用),最后提供一个build方法用于生成被建造的对象。
建造者类可以是一个独立的类,也可以是是被建造类的静态内部类,静态内部类的方式相对来说更加内聚,这里采用这种方式,完整的代码如下所示。

@Getter
public class PersonModel {private String name;private Integer age;private String phone;private String email;// 用于初始化的构造函数public PersonModel(Builder builder) {this.name = builder.name;this.age = builder.age;this.phone = builder.phone;this.email = builder.email;}// 建造者类public static class Builder {private String name;private Integer age;private String phone;private String email;public Builder name(String name) {this.name = name;return this;}public Builder age(Integer age) {this.age = age;return this;}public Builder phone(String phone) {this.phone = phone;return this;}public Builder email(String email) {this.email = email;return this;}public PersonModel build() {return new PersonModel(this);}}
}

我们之前提到的参数的验证就可以写在build方法中:

public PersonModel build() {if (StringUtils.isBlank(name)) {throw new RuntimeException("姓名不能为空");}if (age == null) {throw new RuntimeException("年龄不能为空");}if (StringUtils.isBlank(phone) && StringUtils.isBlank(email)) {throw new RuntimeException("联系方式和邮箱不能同时为空");}return new PersonModel(this);
}

2.3.构建对象测试

完成之后就可以通过建造者类来组装和构建对象了,客户端在使用的时候可以灵活的选择要给哪些字段赋值,赋什么值,例如:

@Test
public void testBuildPerson() {PersonModel personModel = new PersonModel.Builder().name("挥之以墨").age(18).phone("123456789").build();
}

此时就会构建出一个personModel对象,而当我们传入的参数不符合要求时就会根据我们build中的写法,抛出异常给出相应的提示。

@Test
public void testBuildPerson() {PersonModel personModel = new PersonModel.Builder().name("挥之以墨").age(18).build();
}

在这里插入图片描述

2.4.使用lombok简化建造者

我们可以从上面看到,建造者类的编写还是比较繁琐的,如果没有build函数中的各种操作需求,可以考虑直接使用lombok来生成建造者类,只需要加上一个@Builder注解即可。

import lombok.Builder;
import lombok.Getter;@Getter
@Builder
public class PersonModelPure {private String name;private Integer age;private String phone;private String email;
}

编译后我们通过IDE打开.class文件,可以看到反编译的代码如下,生成了一个建造者类:
在这里插入图片描述

需要注意的是,在使用lombok来简化建造者之后,我们在源代码中由于没有建造者的源码,所以无法给Person对象中的字段赋予默认值直接在Person类的字段上赋值是无效的,会被构造方法覆盖。此时需要使用@Builder.Default来标记有默认值的字段:

@Getter
@Builder
public class PersonModelPure {private String name;@Builder.Defaultprivate Integer age = 18;private String phone;private String email;
}

在这里插入图片描述
在这里插入图片描述
在反编译的代码中可以看到,如果没有给age赋值,则会取默认值。


2.5.lombok简化建造者的缺陷

虽然我们可以使用lombok来简化建造者,但我们现在抛开实现回来想一想使用建造者模式的目的是什么?
我们希望在创建对象的时候,有这么一个内聚的对象,能够自由的给对象组装不同的字段的能力,同时希望在字段与字段之间能够形成一点的联动、校验,满足一些特定的要求。

而使用lombok来生成的建造者,能够满足这样的要求吗?
显然,它只是提供了一个自动生成建造者的能力,而没有关心生成这个建造者的目的是什么,所以它并不能满足我们使用建造者的需要。
如果说只是为了组装构建对象而编写一个建造者,相对于new出对象之后,再一个一个set字段值就没有什么优势了,后者编写起来还更加简单。

3.总结

本篇描述了如何实践建造者模式,同时也聊到了为什么要使用建造者模式。
简单的说,就是建造者模式提供了一种能力,让我们在自由的组装构建对象的同时,给到一定的约束。而这样的能力往往是用在框架代码中的,由架构提供建造者,开发者按照建造者的约束去相对自由的构建需要的对象。
在业务代码中,如果我们并没有太多这样的校验的要求,只是希望创建一个对象,直接newset又何乐不为呢?


最后,如果觉得本文有所帮助的话,可以点赞收藏哦~
如果您有不同的见解,也可以留言一同讨论,共同学习进步!

相关文章:

【设计模式】使用建造者模式组装对象并加入自定义校验

文章目录 1.前言1.1.创建对象时的痛点 2.建造者模式2.1 被建造类准备2.2.建造者类实现2.3.构建对象测试2.4.使用lombok简化建造者2.5.lombok简化建造者的缺陷 3.总结 1.前言 在我刚入行不久的时候就听说过建造者模式这种设计模式,当时只知道是用来组装对象&#xf…...

简单聊聊低代码

在数字经济迅速发展的背景下,越来越多的企业开始建立健全业务系统、应用、借助数字化工具提升管理效率,驱动业务发展,促进业绩增长。在这一过程中,和许多新技术一样,低代码(Low-code)开发被推上…...

SystemVerilog Assertions应用指南 第一章(1.27章节 “within”运算符)

“ within”构造允许在一个序列中定义另一个序列。 seq1 within seq2 这表示seq1在seq2的开始到结束的范围内发生,且序列seq2的开始匹配点必须在seq1的开始匹配点之前发生,序列seq1的结束匹配点必须在seq2的结束匹配点之前结束。属性p32检查序列s32a在信号“ start”的上升沿和…...

2023年09月 C/C++(七级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 Python编程(1~6级)全部真题・点这里 第1题:红与黑 有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。请写一个程序,计算你总共能够到达多少块黑色的瓷砖。 时间限…...

[Mono Depth/3DOD]单目3D检测基础

1. 数据增强 图像放缩和裁剪后,相机内参要做相应变化 import random def random_scale(image, calib, scale_range(0.8, 1.2)):scale random.uniform(*scale_range)width, height image.sizeimage image.resize((int(width * scale), int(height * scale)))cal…...

【Docker 内核详解】namespace 资源隔离(三):PID namespace

namespace 资源隔离(三):PID namespace 1.PID namespace 中的 init 进程2.信号与 init 进程3.挂载 proc 文件系统4.unshare() 和 setns() PID namespace 隔离非常实用,它对进程 PID 重新标号,即两个不同 namespace 下的…...

1600*C. Game On Leaves(博弈游戏树)

Problem - 1363C - Codeforces 解析: 我们将目标结点 x 当作树的根,显然,到当 x 的度为 1 的时候,此时行动的人胜利。 我们假设现在的情况为,只剩余三个点,再选择任意一个点,则对方获胜。但是两…...

Apache Ant的安装

介绍 Apache Ant是一个Java库和一个 命令行工具,可以用来构建Java应用。Ant提供了许多内置的任务(tasks),可以编译、组装、测试、运行Java应用。Ant也可以构建非Java应用,例如C、C应用。 Ant非常灵活,没有…...

考研:数学二例题--∞−∞和0⋅∞型极限

前言 本文只是例题,建议先参考具体如何做这类型例题。请到主文章中参考:https://blog.csdn.net/grd_java/article/details/132246630 ∞ − ∞ 和 0 ⋅ ∞ \infin - \infin 和 0\infin ∞−∞和0⋅∞ 例题 例1: lim ⁡ x → ∞ x 2 x 2 −…...

C++算法:图中的最短环

题目 现有一个含 n 个顶点的 双向 图,每个顶点按从 0 到 n - 1 标记。图中的边由二维整数数组 edges 表示,其中 edges[i] [ui, vi] 表示顶点 ui 和 vi 之间存在一条边。每对顶点最多通过一条边连接,并且不存在与自身相连的顶点。 返回图中 …...

C++学习——类其实也是一种作用域

以下内容源于C语言中文网的学习与整理,非原创,如有侵权请告知删除。 其实也是一种作用域,每个类都会定义它自己的作用域。在类的作用域之外,普通的成员只能通过对象(可以是对象本身,也可以是对象指针或对象…...

Seata入门系列【4】undo_log、global_table、branch_table、lock_table字段及作用详解

1 客户端 1.1 undo_log 在AT模式中,需要在参与全局事务的数据库中,添加一个undo_log表,建表语句如下: SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for undo_log -- --…...

虚幻引擎:数据表格的C++常用API

1.将数据表格中的所有数据存到一个数组中 //参数1 // 错误提示 //参数2 // 存储的数组 TArray<FKeyInfoHeader*> array; KeyInfoDT->GetAllRows<FKeyInfoHeader>(TEXT("错误"),array); 2.获取表格中所有的行名称 TArray<FName>array; …...

Java日期格式化(DateFormat类和SimpleDateFormat类)

格式化日期表示将日期/时间格式转换为预先定义的日期/时间格式。例如将日期“Fri May 18 15:46:24 CST2016” 格式转换为 “2016-5-18 15:46:24 星期五”的格式。 在 java 中&#xff0c;可以使用 DateFormat 类和 SimpleDateFormat 类来格式化日期&#xff0c;下面详细介绍这两…...

centos 7 lamp owncloud

OwnCloud是一款开源的云存储软件&#xff0c;基于PHP的自建网盘。基本上是私人使用&#xff0c;没有用户注册功能&#xff0c;但是有用户添加功能&#xff0c;你可以无限制地添加用户&#xff0c;OwnCloud支持多个平台&#xff08;windows&#xff0c;MAC&#xff0c;Android&a…...

屏幕亮度调节保护您的眼睛

官方下载地址&#xff1a; 安果移动 视频演示&#xff1a;屏幕亮度调节-保护您的眼睛_哔哩哔哩_bilibili 嗨&#xff0c;亲爱的用户&#xff0c;你是否有过这样的体验&#xff1a;夜晚安静的时刻&#xff0c;想要在抖音上看看热门的舞蹈、在快手上发现生活的 趣味、或是在哔…...

CentOS Linux下CMake二进制文件安装并使用Visual Studio调试

cmake安装——二进制安装(很简单&#xff0c;推荐&#xff01;&#xff01;) 1&#xff09;下载二进制包。首先就是官网下载二进制安装包(我们是64位系统&#xff0c;就下载对应的包)&#xff0c;这里。 例如&#xff1a;在/home/DOWNLOAD目录下执行&#xff0c;即下载二进制…...

ASP.net相关目录,相关配置文件和.后缀名解释

App_Data&#xff1a;用于存储应用程序的数据文件&#xff0c;例如数据库文件或其他本地文件。 App_Start&#xff1a;包含应用程序的启动配置文件&#xff0c;例如路由配置、日志配置等。 Content&#xff1a;存放应用程序的静态资源文件&#xff0c;如 CSS、JavaScript、图…...

一键批量转换,轻松将TS视频转为MP4视频,实现更广泛的播放和分享!

在享受精彩视频内容的同时&#xff0c;有时我们可能会面临一个问题&#xff1a;某些视频格式可能不太适合我们的播放设备或分享平台。特别是TS格式的视频&#xff0c;在一些情况下可能无法直接播放或上传。但是不用担心&#xff0c;因为我们为您提供了一款强大的视频剪辑工具&a…...

【Redis】使用Java客户端操作Redis

目录 引入jedis依赖连接Redis命令get/setexists/delkeysexpire/ttltype 引入jedis依赖 连接Redis 命令 get/set exists/del keys expire/ttl type...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...