Effective Java 2 遇到多个构造器参数时要考虑使用构建器

第2个经验法则:用遇到多个构造器参数时要考虑使用构建器(consider a builder when faced with many constructor parameters)

上一条讨论了静态工厂相对于构造器来说有五大优势。但静态工厂和构造器有个共同的局限性:它 们都不能很好地扩展到大量的可选参数。

对于需要多参数的类,应该用哪种构造器或者静态工厂来编写呢? 接下来,我将通过Java代码示例来对比分析构造器模式、JavaBeans模式以及建造者模式在处理多参数情况下的应用。

构造器模式

假设我们要创建一个 Car 类,它有颜色、品牌、型号和价格等属性,其中颜色和品牌是必需的, 而型号和价格是可选的。

这其实就是重叠构造器(telescoping constructor)模式。程序员一向习惯采用这种模式,在这种模式下,提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,依此类推,最后一个构造器包含所有可选的参数。随着参数增多,构造器的重载会变得复杂且难以管理。

简而言之,重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写并且仍然较 难以阅读。如果读者想知道那些值是什么意思,必须很仔细地数着这些参数来探个究竟。一长串 类型相同的参数会导致一些微妙的错误。如果客户端不小心颠倒了其中两个参数的顺序,编译器 也不会出错,但是程序在运行时会出现错误的行为。

JavaBeans模式

采用JavaBeans模式,我们首先定义一个无参构造器,然后通过setter方法设置属性。

这种模式弥补了重看构造器模式的不足。说得明白一点,就是创建实例很容易,这样产生的代码 读起来也很容易。

遗憾的是,JavaBeans模式自身有着很严重的缺点。因为构造过程被分到了几个调用中在构造过 程中,JavaBean 可能处于不一致的状态,导致对象在完全配置前处于不一致状态。尤其是在多线程环境下或构建过程较长的情境中。下面通过一个具体例子来进一步说明这一问题:

假设我们有一个 Order 类,用于表示在线商店中的订单信息,包括客户ID、商品列表、总价等属 性。使用JavaBeans模式,类定义如下:

假设在某个服务中,我们打算创建一个订单并填充相关信息,但这个过程是分步进行的,可能涉 及多个操作或方法调用,如下所示:

在这个例子中,如果在设置完商品ID之后,程序因为某些原因(如异常抛出、线程切换)没有机 会执行设置总价的逻辑,那么 Order对象就被留在了一个不一致的状态:它有客户ID和商品列 表,但缺少了总价信息。如果此时对象被其他部分的系统使用,可能会引发逻辑错误或计算问题。

另外,在多线程环境下,如果不加锁或其他同步措施,多个线程同时调用setter方法设置不同属性,还可能引起竞态条件,进一步加剧数据的不一致性。

因此,虽然JavaBeans模式提供了灵活性,但在构建过程中必须谨慎管理对象状态的完整性,特 别是在多步骤或多线程场景中,以避免数据不一致的问题。相比之下,建造者模式在这种情况下 提供了更好的解决方案,因为它能确保对象在构建完成之前是一个完整且一致的状态。

建造者模式 (Builder Pattern)

建造者模式通过引入Builder类来解决上述问题(既能保证像重看构造器模式那样的安全性,也能保证像JavaBeans模式那么好的可读性),保持了代码的清晰度和对象的完整性。

建造者模式通过链式调用来设置参数,既保证了代码的可读性,也确保了对象的完整性,尤其适 合参数较多且有可选参数的情况。

它不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到 一个builder对象。然后客户端在 builder 对象上调用类似于 setter 的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成通常是不可变的对象。

在这个过程中,每次创建Car对象,都会先实例化一个Car.CarBuilder对象,然后通过一系列链 式调用来设置属性,最后调用build()方法生成Car对象。虽然Builder模式提供了清晰的构建 逻辑和良好的可读性,但每个Car对象的创建实际上涉及了两次对象实例化:一次是Builder对 象,一次是最终的Car对象。

想象一个高性能的金融交易系统,每秒需要处理数百万次交易请求。为了优化内存使用和减少GC(垃圾回收)的压力,系统中的每一个环节都需尽可能地高效。在这样的系统中,交易对象的创建频繁且量大,哪怕是最小的性能损失也可能在大规模操作中被放大。

Builder模式不仅可以应用于单个类的复杂对象创建,也非常适合应用于类层次结构,以保持代码的一致性和扩展性。下面通过一个电子产品类层次结构的例子来说明这一点:假设我们有一个基本的Electronics类,以及它的两个子类Smartphone和Laptop,每个类都有其特有的属性。

首先,我们定义一个基础的Electronics类和对应的ElectronicsBuilder。抽象的Electronics类代表了一般的电子产品,包含了一些基础属性,比 如品牌(brand)、型号(model)和价格(price)。同时定义了一个内部抽象类 ElectronicsBuilder,作为构建电子产品实例的模板。这个Builder类定义了一些通用的设置方法(如设置品牌、型号和价格),并且通过泛型参数 T 来确保Builder自身类型的安全返回,即所谓的 fluent interface(流畅接口)设计,让设置过程可以链式调用。

接下来,定义Smartphone类,它继承自Electronics,并新增了特有的属性 storageCapacity。相应的,我们也会创建一个SmartphoneBuilder来构建Smartphone实例。 SmartphoneBuilder继承自E lectronicsBuilder 。 SmartphoneBuilder 除了继承来的通用设置方法外,还添加了一个设置 存储容量的方法。通过覆写 self() 方法返回当前Builder的类型,确保了类型安全和链式调用的延续。

同样地,定义Laptop类,对应的LaptopBuilder负责构建Laptop实例。Laptop类有自己的特性属性——屏幕尺寸 (screenSize )。对应的 LaptopBuilder 同样继承自ElectronicsBuilder的方法,并实现了自己的 self() 和 ElectronicsBuilder ,添加了设置屏幕尺 build() 方法,以适应 Laptop 的构建需求。

现在,可以这样使用Builder模式来创建不同类型的电子产品,并保持代码的清晰和类型安全:

通过这种方式,Builder模式不仅解决了复杂对象的构建问题,而且在类层次结构中保持了良好的扩展性和一致性,使得每个子类都能拥有自己特性的Builder,同时复用了基类Builder的部分逻辑,减少了代码重复,提升了代码的可维护性。

Builder 模式的确也有它自身的不足:

为了创建对象,必须先创建它的构建器。虽然创建这个构建器的开销在实践中可能不那么明显,但是在某些十分注重性能的情况下,可能就成问题了。 

Builder模式还比重看构造器模式更加冗长,因此它只在有很多参数的时候才使用,比如4个 或者更多个参数。

简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是一 种不错的选择。 特别是当大多数参数都是可选或者类型相同的时候。与使用重叠构造器模式相比,使用Builder 模式的客户端代码将更易干阅读和编写,构建器也比JavaBeans加安全。

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

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

相关文章

springcloudalibaba项目注册nacos,在nacos上修改配置项不生效问题

一、背景 之前的项目启动正常,后来发现springcloudalibaba的各版本匹配不正确,于是对项目中的springboot、springcloud、springcloudalibaba版本进行匹配升级,nacos1.4.2匹配的springboot、springcloud、springcloudalibaba版本与我的项目中的版本比较接近,于是我便重新安…

零基础入门篇①⑦ Python可变序列类型--集合

Python从入门到精通系列专栏面向零基础以及需要进阶的读者倾心打造,9.9元订阅即可享受付费专栏权益,一个专栏带你吃透Python,专栏分为零基础入门篇、模块篇、网络爬虫篇、Web开发篇、办公自动化篇、数据分析篇…学习不断,持续更新,火热订阅中🔥专栏限时一个月(5.8~6.8)重…

算法家族之一——二分法

目录 算法算法的打印效果如果算法里的整型“i”为1如果算法里的整型“i”为11 算法的流程图算法的实际应用总结 大家好&#xff0c;我叫 这是我58&#xff0c;现在&#xff0c;请看下面的算法。 算法 #define _CRT_SECURE_NO_WARNINGS 1//<--预处理指令 #include <stdi…

中国宠业新锐品牌展,2024苏州国际宠物展6月28日开展!

中国宠业新锐品牌展&#xff0c;2024苏州国际宠物展6月28日开展&#xff01; ​ 第2届华东国际宠物用品展览会(苏州)暨中国宠业新锐品牌展&#xff0c;将于6月28日-30日在苏州国际博览中心盛大举办&#xff0c;锁定年中市场黄金档期&#xff0c;同期以“NB展&#xff0c;更新鲜…

非线性模型预测控制NMPC例子

NMPC概述 非线性模型预测控制(Nonlinear Model Predictive Control, NMPC)是一种用于控制非线性系统的高级控制策略。与线性MPC不同,NMPC需要处理系统的非线性特性,这使得优化问题更加复杂。NMPC通常使用迭代优化算法来求解非线性优化问题 NMPC基本原理 NMPC的目标是最小…

微服务之基本介绍

一、微服务架构介绍 1、微服务架构演变过程 单体应用架构->垂直应用架构一>分布式架构一>SOA架构-->微服务架构 ① 单体应用架构 互联网早期&#xff0c; 一般的网站应用流量较小&#xff0c;只需一个应用&#xff0c;将所有功能代码都部署在一起就可以&#x…

从哲学层面谈稳定性建设

背景 我&#xff08;姓名&#xff1a;黄凯&#xff0c;花名&#xff1a;兮之&#xff09;在阿里工作了五年&#xff0c;一直在一个小团队从事电商的稳定性工作。看了很多稳定性相关的文档&#xff0c;很少有能把稳定性说明白的文档。也有一些文档也能把涉及的方方面面说清楚&a…

【代码随想录】【算法训练营】【第32天】 [122]买卖股票的最佳时机II [376]摆动序列 [53]最大子序和

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 32&#xff0c;一个不上班的周六&#xff0c;坚持一了一点~ 题目详情 [122] 买卖股票的最佳时机II 题目描述 122 买卖股票的最佳时机II 解题思路 前提&#xff1a;单链表 删除元素 思路&a…

Linux基础I/O

一&#xff0c;系统文件I/O 写文件: #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> int main() {umask(0);int fd open("myfile", O_WRO…

doris FE 在Windows环境下编译调试开发环境

前言&#xff1a; doris fe 在win下调试运行&#xff0c;和正常java项目有一些差异&#xff0c;主要是有与be&#xff08;c&#xff09;通信代码的生成 在win环境下不能直接生成&#xff0c;因此需要现在linux下生成之后&#xff0c;再拷贝到本地来&#xff0c;然后进行编译&a…

C++笔试强训day42

目录 1.最大差值 2.兑换零钱 3.小红的子串 1.最大差值 链接https://www.nowcoder.com/practice/a01abbdc52ba4d5f8777fb5dae91b204?tpId182&tqId34396&rp1&ru/exam/company&qru/exam/company&sourceUrl%2Fexam%2Fcompany&difficulty2&judgeSta…

每日5题Day19 - LeetCode 91 - 95

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;91. 解码方法 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int numDecodings(String s) {int n s.length();//注意我们dp的范围是n1int[] d…

面试官:前端实现图片懒加载怎么做?这不是撞我怀里了嘛!

前端懒加载&#xff08;也称为延迟加载或按需加载&#xff09;是一种网页性能优化的技术&#xff0c;主要用于在网页中延迟加载某些资源&#xff0c;如图片、视频或其他媒体文件&#xff0c;直到它们实际需要被用户查看或交互时才进行加载。这种技术特别适用于长页面或包含大量…

【JavaEE】Spring IoCDI详解

一.基本概念 1.Ioc基本概念 Ioc: Inversion of Control (控制反转), 也就是说 Spring 是⼀个"控制反转"的容器. 什么是控制反转呢? 也就是控制权反转. 什么的控制权发发了反转? 获得依赖对象的过程被反转了也就是说, 当需要某个对象时, 传统开发模式中需要自己通…

美银美林:看好铜价涨到12000美元,这类铜矿企业弹性更大

美银美林指出&#xff0c;考虑到能源转型以及AI投资热潮对铜的需求巨大&#xff0c;到2026年铜供需缺口有望扩大一倍。给予紫金矿业、江西铜业等多家巨头买入评级&#xff0c;并认为一旦铜价上行&#xff0c;KGHM等规模较小、成本较高的企业的利润增长可能更为显著。 高盛、花…

python中while循环实现九九乘法表

i 1while i < 9: # 控制行的循环j 1while j < i: # 控制每行的输出print(f"{j}*{i}{j * i}\t", end"")j 1print()i 1运行截图&#xff1a;

Cweek4+5

C语言学习 十.指针详解 6.有关函数指针的代码 代码1&#xff1a;(*(void (*)())0)(); void(*)()是函数指针类型&#xff0c;0是一个函数的地址 (void(*)())是强制转换 总的是调用0地址处的函数&#xff0c;传入参数为空 代码2&#xff1a;void (*signal(int, void(*)(int))…

ENSP校园网设计实验

前言 哈喽&#xff0c;我是ICT大龙。本次更新了使用ENSP仿真软件设计校园网实验。时间比较着急&#xff0c;可能会有错误&#xff0c;欢迎大家指出。 获取本次工程文件方式在文章结束部分。 拓扑设计 拓扑介绍---A校区 如图&#xff0c;XYZ大学校园网设计分为3部分&#xff0…

JDK下载安装Java SDK

Android中国开发者官网 Android官网 (VPN翻墙) 方案1&#xff1a;通过brew命令 下载OracleJDK(推荐) brew --version //是否已存在homebrew环境 brew install --cask oracle-jdk //通过brew命令直接下载并安装OracleJDK(VPN、登录密码) java -version //验证JDK是否下载并安装…

UI学习的案例——照片墙

照片墙案例 在实现照片墙案例之前先讲一下userInteractionEnable这个属性。 首先这个属性属于UIView&#xff0c;这个属性是bool类型&#xff0c;如果为YES的话&#xff0c;这个UIView会接受有关touch和keyboard的相关操作&#xff0c;然后UIView就可以通过相应的一些方法来处…