当前位置: 首页 > 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...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用于…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

vulnyx Blogger writeup

信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面&#xff0c;gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress&#xff0c;说明目标所使用的cms是wordpress&#xff0c;访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...

【Android】Android 开发 ADB 常用指令

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

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...