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

文章目录

  • 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又何乐不为呢?


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

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

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

相关文章

Vuex存值取值与异步请求处理

目录 前言 一、Vuex简介 1.Vuex是什么 2.Vuex的核心概念 3.使用Vuex的好处 4.Vuex执行流程 二、Vuex的使用步骤 1.安装Vuex 2.创建store模块,分别维护state/actions/mutations/getters 3.使用Vuex存储值,获取值和改变值 1.state.js---存值 2.…

关于Vuex的基础使用存值及异步

目录 一.概述 二.取值 2.1.安装 2.2.菜单栏 2.3.模块 2.4.引用 三.改值 四.异步&后台请求 好啦今天就到这里了希望能帮到你哦!!! 一.概述 Vuex 是一个用于 Vue.js 应用程序的状态管理库。它主要用于集中管理应用程序中的共享状态&a…

idea中maven plugin提示not found

在终端中输入: mvn dependency:resolve 然后 解决了部分问题 Plugin org.apache.maven.plugins:maven-jar-plugin:3.1.0 not found 改为3.3.0了 Plugin maven-source-plugin:3.3.0 not found 改为 2.4 了 版本下降了 感觉后继有坑 待观察

Linux网络和系统管理

网络管理命令 1、ifconfig 命令 作用 ifconfig 命令用于显示或设置网络设备的信息。格式 ifconfig [网卡名字] [参数]可选项 网卡名字:指定要操作的网络设备。参数: up:启动指定网卡。down:关闭指定网卡。-a:显示所有网卡接口的信息,包括未激活的网卡接口。使用示例 1…

时代风口中的Web3.0基建平台,重新定义Web3.0!

近年来,Web3.0概念的广泛兴起,给加密行业带来了崭新的叙事方式,同时也为加密行业提供了更加具有想象力的应用场景与商业空间,并让越来越多的行业从业者们意识到只有更大众化的市场共性需求才能推动加密市场的持续繁荣。当前围绕这…

IDEA设置自动导入包

IDEA设置自动导入包 首先进入设置选项 之后勾选以下两项: 第一项:IntelliJ IDEA 将在我们书写代码的时候自动帮我们优化导入的包,比如自动去掉一些没有用到的包。 第二项: IntelliJ IDEA 将在我们书写代码的时候自动帮我们导入…

.NET ABP.Zero 项目疑似内存排查历程

当前项目是 .NET 5 EentityFrameworkCore,疑似内存泄漏,之所以说是疑似是因为到目前位置还没有能准确的定位到问题。当前这个框架从 .NET Core 2.1 就开始用,期间有升级到 3.1、5.0、6.0,在排查过程中还把 5.0 分支升级到了 7.0 。…

HashMap -- 调研

HashMap 调研 前言JDK1.8之前拉链法: JDK1.8之后JDK1.7 VS JDK1.8 比较优化了一下问题: HashMap的put方法的具体流程?HashMap的扩容resize操作怎么实现的? 前言 在Java中,保存数据有两种比较简单的数据结构:数组和链表。 数组的特点是:寻址容易,插入…

【RabbitMQ 实战】11 队列的结构和惰性队列

一、 队列的结构 队列的组成: 队列由 rabbit_amgqueue_process 和 backing_queue两部分组成。rabbit_amqqueue_process负责协议相关的消息处理,即接收生产者发布的消息、向消费者交付消息、处理消息的确认 (包括生产端的 confirm 和消费端的 ack) 等。…

Qt/C++原创推流工具/支持多种流媒体服务/ZLMediaKit/srs/mediamtx等

一、前言 1.1 功能特点 支持各种本地视频文件和网络视频文件。支持各种网络视频流,网络摄像头,协议包括rtsp、rtmp、http。支持将本地摄像头设备推流,可指定分辨率和帧率等。支持将本地桌面推流,可指定屏幕区域和帧率等。自动启…

【Vuex+ElementUI】Vuex中取值存值以及异步加载的使用

一、导言 1、引言 Vuex是一个用于Vue.js应用程序的状态管理模式和库。它建立在Vue.js的响应式系统之上,提供了一种集中管理应用程序状态的方式。使用Vuex,您可以将应用程序的状态存储在一个单一的位置(即“存储”)中,…

iPhone15手机拓展坞方案,支持手机快充+传输数据功能

手机拓展坞的组合有何意义?首先是数据存储场景,借助拓展坞扩展出的接口,可以连接U盘、移动硬盘等采用USB接口的设备,实现大文件的快速存储或者流转;其次是图片、视频的读取场景,想要读取相机、无人机SD/TF存…

【angular】实现简单的angular国际化(i18n)

文章目录 目标过程运行参考 目标 实现简单的angular国际化。本博客实现中文版和法语版。 将Hello i18n!变为中文版:你好 i18n!或法语版:Bonjour l’i18n !。 过程 创建一个项目: ng new i18nDemo在集成终端中打开。 添加本地化包: ng a…

042:mapboxGL点击某feature点,使其为中心点

第042个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中通过鼠标点击某feature点,让其成为中心点。这里用到了click事件和flyTo的方法。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共113行)相关API参…

spring boot+ vue位置信息大数据综合管理平台源码

spring boot vue位置信息大数据综合管理平台源码 UWB技术的人员定位系统源码 智慧工厂是产业升级的外在表现形式,利用物联网技术加强信息管理的新模式,人员定位管理通过物联网技术、位置信息大数据的综合处理应用,在智慧工厂人员管理方面具有…

大模型之Prompt研究和技巧

大模型之Prompt研究和技巧 大模型之Prompt编写简介组成技术Zero-ShotFew-shotCOTCOT-SCTOTGoTReAct 大模型之Prompt编写 简介 Prompt是是给 AI **模型的指令,**一个简短的文本输入,用于引导AI模型生成特定的回答或执行特定任务。 Prompt是你与语言模型沟…

【方法】PDF不能转换成其它格式如何解决?

想把PDF文件转换成其他格式,比如Word、PPT,却发现无法操作,这是什么情况呢?又该如何解决?下面我们一起来看看吧。 原因1:没有使用PDF编辑器 如果是在线打开PDF,或者使用PDF阅读器打开PDF&…

将 mysql 数据迁移到 clickhouse (最新版)

一、前驱知识 已经在mysql中插入了海量的数据了,这个时候mysql 承载不了这么大的数据,并且数据只需要查询,修改和删除非常少,并且不需要支持事务,这个时候需要换一个底层存储,这里选用的是 clickhouse 来进…

【论文阅读】面向抽取和理解基于Transformer的自动作文评分模型的隐式评价标准(实验结果部分)

方法 结果 在这一部分,我们展示对于每个模型比较的聚合的统计分析当涉及到计算特征和独立的特征组(表格1),抽取功能组和对齐重要功能组(表格2),并且最后,我们提供从模型比较&#x…

解读非托管流动性协议Hover: 差异化、层次化的全新借贷体系

“Hover 是 DeFi 借贷赛道的另辟蹊径者,除了在自身机制(借贷模型、治理体系)上进行创新获得内生动力外,背靠日渐繁荣的 Kava、Cosmos 生态进一步获得外生动力,发展潜力俱佳” 与 DEX 类似,借贷也是 DeFi 世…