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

创建和销毁对象——遇到多个构造器参数时要考虑使用构建器

静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。比如用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的:每份的含量、每罐的含量以及每份的卡路里。还有超过20个的可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。大多数产品在某几个可选域中都会有非零的值。 

对于这样的类,应该用哪种构造器或者静态工厂来编写呢?程序员一向习惯采用重叠构造器模式,在这种模式下,提供的第一个构造器只有必要的参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数,以此类推,最后一个构造器包含所有可选的参数。下面有个示例,为了简单起见,它只显示四个可选域:

final修饰的变量表示赋值之后不能再进行更改,系统赋默认值也算赋值,因此系统也不会赋默认值

/*** 营养成分*/
public class NutritionFacts {private final int servingSize; //  每份含量             requiredprivate final int servings; //    每罐含量     requiredprivate final int calories;// 卡路里/罐      optionalprivate final int fat;//   脂肪/罐              optionalprivate final int sodium; //   钠/罐         optionalprivate final int carbohydrate; // 碳水/罐     optionalpublic NutritionFacts(int servingSize, int servings) {this(servingSize, servings, 0);}public NutritionFacts(int servingSize, int servings, int calories) {this(servingSize, servings, calories, 0);}public NutritionFacts(int servingSize, int servings, int calories, int fat) {this(servingSize, servings, calories, fat, 0);}public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {this(servingSize, servings, calories, fat, sodium, 0);}public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {this.servingSize = servingSize;this.servings = servings;this.calories = calories;this.fat = fat;this.sodium = sodium;this.carbohydrate = carbohydrate;}
} 

当你想要创建实例的时候,就利用参数列表最短的构造器,该列表中包含了要设置的所有参数: 

NutritionFacts cocaCola = new NutritionFacts(240 ,8,100,0,35,27); 

这个构造器调用通常需要许多你本不想设置的参数,但还是不得不为它们传递值。在这个例子中,我们给fat 传递了一个值为0 。 如果“仅仅”是这6个参数,看起来还不算太糟糕,问题是随着参数数目的增加,它很快就失去了控制。

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

遇到许多可选的构造器参数的时候,还有第二种代替办法,即JavaBeans模式,在这种模式下,取消掉final修饰符,先调用一个无参构造器来创建对象,然后再调用setter 方法来设置每个必要的参数,以及每个相关的可选参数

/*** 营养成分*/
public class NutritionFacts {private  int servingSize; //  每份含量             requiredprivate  int servings; //    每罐含量     requiredprivate  int calories;// 卡路里/罐      optionalprivate  int fat;//   脂肪/罐              optionalprivate  int sodium; //   钠/罐         optionalprivate  int carbohydrate; // 碳水/罐     optionalpublic int getServingSize() {return servingSize;}public void setServingSize(int servingSize) {this.servingSize = servingSize;}public int getServings() {return servings;}public void setServings(int servings) {this.servings = servings;}public int getCalories() {return calories;}public void setCalories(int calories) {this.calories = calories;}public int getFat() {return fat;}public void setFat(int fat) {this.fat = fat;}public int getSodium() {return sodium;}public void setSodium(int sodium) {this.sodium = sodium;}public int getCarbohydrate() {return carbohydrate;}public void setCarbohydrate(int carbohydrate) {this.carbohydrate = carbohydrate;}
}

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

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

遗憾的是,JavaBeans 模式自身有着很严重的缺点。因为构造过程被分到了几个调用中, 在构造过程中,JavaBeans 可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象将会导致失败,这种失败与包含错误的代码大相径庭,因此调试起来十分困难。与此相关的另一点不足在于,JavaBeans 模式使得把类做成不可变的可能性不复存在,这就需要程序员付出额外的努力来确保它的线程安全。

幸运的是,还有第三种替代方法,它既能保证像重叠构造器模式那样的安全性,也能保证像JavaBeans 那么好的可读性。这就是建造者(Builder)模式的一种形式,它不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder 对象。然后客户端在builder 对象上调用类似于setter 的方法,来设置每个相关的可选参数。最后,客户端调用无参的build 方法来生成通常是不可变的对象。这个builder 通常是它构建的类的静态成员类,下面就是它的示例:

/*** 营养成分*/
public class NutritionFacts {private final int servingSize; //  每份含量             requiredprivate final int servings; //    每罐含量     requiredprivate final int calories;// 卡路里/罐      optionalprivate final int fat;//   脂肪/罐              optionalprivate final int sodium; //   钠/罐         optionalprivate final int carbohydrate; // 碳水/罐     optionalprivate NutritionFacts(Builder builder) {servingSize = builder.servingSize;servings = builder.servings;calories = builder.calories;fat = builder.fat;sodium= builder.sodium;carbohydrate = builder.carbohydrate;} public static class Builder{private int servingSize; // (ml) 每份含量             requiredprivate int servings; // (per container) 每罐含量     requiredprivate int calories;// (per serving) 卡路里/每罐      optionalprivate int fat;// (g/serving)脂肪  g/罐              optionalprivate int sodium; // (mg/serving) 钠  mg/罐         optionalprivate int carbohydrate; //(g/serving) 碳水 g/罐     optionalpublic Builder(int servingSize,int servings){this.servingSize = servingSize;this.servings = servings;}public Builder calories(int val){calories = val;return this;}public Builder fat(int val){fat = val;return this;}public Builder sodium(int val){sodium = val;return this;}public Builder carbohydrate(int val){carbohydrate = val;return this;}public NutritionFacts build(){return new NutritionFacts(this);}}
}

注意 NutritionFacts 是不可变的,所有的默认参数值都单独放在一个地方。builder的设置方法返回自身,以便把调用链接起来,得到一个流式API。下面就是其客户端代码:

NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).calories(100).sodium(35).carbohydrate(27).build(); 

这样的客户端代码很容易编写,更为重要的是易于阅读。Builder 模式模拟了可选参数

为了简洁起见,示例中省略了有效性检查。要想尽快侦测到无效的参数,可以在builder 的构造器和方法中检查参数的有效性。查看不可变量,包括build方法调用的构造器中的多个参数。为了确保这些不变量免受攻击,从builder 复制完参数之后,要检查对象域(详见第50条)。如果检查失败就抛出 IllegalArgumentException,其中的详细信息会说明哪些参数是无效的。

与构造器相比,builder 的微弱优势在于,它可以有多个可变(varargs)参数。因为builder 是利用单独的方法来设置每一个参数。

Builder 模式的确也有它自身的不足。为了创建对象,必须先创建它的构建器。虽然创建这个构建器的开销在实践中可能不那么明显,但是在某些十分注重性能的情况下,可能就成问题了。Builder 模式还比重叠构造器模式更加冗长,因此它只在有很多参数的时候才使用,比如4个或更多。但是记住,将来你可能需要添加参数。如果一开始就使用构造器或静态工厂,等到类需要多个参数时才添加构造器,就会无法控制,那些过时的构造器或者静态工厂显得十分不协调。因此,通常最好一开始就使用构建器(Builder)。

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


如果是内部调用较多,确定没有安全问题,JavaBean模式和builder 已经非常接近了,并且,可以使用链式调用的方式,让JavaBean 的调用显得更简洁,更像builder 

/*** 营养成分*/
public class NutritionFacts {private int servingSize; //  每份含量             requiredprivate int servings; //    每罐含量     requiredprivate int calories;// 卡路里/罐      optionalprivate int fat;//   脂肪/罐              optionalprivate int sodium; //   钠/罐         optionalprivate int carbohydrate; // 碳水/罐     optionalpublic NutritionFacts setServingSize(int servingSize) {this.servingSize = servingSize;return this;}public NutritionFacts setServings(int servings) {this.servings = servings;return this;}public NutritionFacts setCalories(int calories) {this.calories = calories;return this;}public NutritionFacts setFat(int fat) {this.fat = fat;return this;}public NutritionFacts setSodium(int sodium) {this.sodium = sodium;return this;}public NutritionFacts setCarbohydrate(int carbohydrate) {this.carbohydrate = carbohydrate;return this;}public static void main(String[] args) {NutritionFacts cocaCola = new NutritionFacts().setServingSize(240).setServings(8).setCalories(100).setSodium(35).setCarbohydrate(27);} 
}

当然,现在很多公司和团队已经开始积极使用Lombok 来简化bean 对象。可以如下

/*** 营养成分*/
@Getter
@Setter
@Accessors(chain = true)
public class NutritionFacts {private int servingSize; //  每份含量             requiredprivate int servings; //    每罐含量     requiredprivate int calories;// 卡路里/罐      optionalprivate int fat;//   脂肪/罐              optionalprivate int sodium; //   钠/罐         optionalprivate int carbohydrate; // 碳水/罐     optionalpublic static void main(String[] args) {NutritionFacts cocaCola = new NutritionFacts().setServingSize(240).setServings(8).setCalories(100).setSodium(35).setCarbohydrate(27);}
}

相关文章:

创建和销毁对象——遇到多个构造器参数时要考虑使用构建器

静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。比如用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的:每份的含量、每罐的含量以及每份的卡路里。还有超过20个的可选域:总脂肪量、饱和脂…...

【c++学习】入门c++(中)

目录一. 前言二. 函数重载1. 概念2.函数名修饰规则三 .引用(&)1. 概念2. 引用特性3.应用1.做参数2. 做返回值3. 传值、传引用效率比较4.引用和指针的区别四 . 结语一. 前言 小伙伴们大家好,今天我们继续学习c入门知识,今天的…...

论文阅读_AlphaGo_Zero

论文信息 name_en: Mastering the game of Go without human knowledge name_ch: 在没有人类知识的情况下掌握围棋游戏 paper_addr: http://www.nature.com/articles/nature24270 doi: 10.1038/nature24270 date_publish: 2017-10-01 tags: [‘深度学习’,‘强化学习’] if: 6…...

一文教你用Python创建自己的装饰器

python装饰器在平常的python编程中用到的还是很多的,在本篇文章中我们先来介绍一下python中最常使用的staticmethod装饰器的使用。 目录一、staticmethod二、自定义装饰器python类实现装饰器python函数嵌套实现装饰器多个装饰器调用三、带参数的装饰器一、staticmet…...

华为OD机试 - 任务总执行时长(JS)

任务总执行时长 题目 任务编排服务负责对任务进行组合调度。参与编排的任务又两种类型,其中一种执行时长为taskA,另一种执行时长为taskB。任务一旦开始执行不能被打断,且任务可连续执行。服务每次可以编排num个任务。 请编写一个方法,生成每次编排后的任务所有可能的总执…...

pytorch离线快速安装

1.pytorch官网查看cuda版本对应的torch和torchvisionde 版本(ncvv -V,nvidia-sim查看cuda对应的版本) 2.离线下载对应版本,网址https://download.pytorch.org/whl/torch_stable.html 我下载的: cu113/torch-1.12.0%2Bcu113-cp37-cp37m-win_…...

华为OD机试 - 数组合并(JS)

数组合并 题目 现在有多组整数数组,需要将他们合并成一个新的数组。 合并规则,从每个数组里按顺序取出固定长度的内容合并到新的数组中, 取完的内容会删除掉, 如果该行不足固定长度或者已经为空, 则直接取出剩余部分的内容放到新的数组中,继续下一行。 如样例1,获得长度3,先遍…...

不要让GPT成为你通向“学业作弊”的捷径——使用GPT检测工具来帮助你保持正确的方向

不要让GPT成为你通向“学业作弊”的捷径——使用GPT检测工具来帮助你保持正确的方向 最近,多所美国高校以及香港大学等都明确禁止在校使用ChatGPT等智能文本生成工具。GPT(Generative Pre-trained Transformer)是一种自然语言处理技术&#x…...

基于matlab的斜视模式下SAR建模

一、前言此示例说明如何使用线性 FM (LFM) 波形对基于聚光灯的合成孔径雷达 (SAR) 系统进行建模。在斜视模式下,SAR平台根据需要从宽侧斜视一定角度向前或向后看。斜视模式有助于对位于当前雷达平台位置前面的区域进行…...

15-基础加强-1-类加载器反射

文章目录1.类加载器1.1类加载器【理解】1.2类加载的过程【理解】1.3类加载的分类【理解】1.4双亲委派模型【理解】1.5ClassLoader 中的两个方法【应用】2.反射2.1反射的概述【理解】2.2获取Class类对象的三种方式【应用】 第1步:获取类的Class对象2.3反射获取构造方…...

基于SSM,Spring, BootStrap 毕业设计管理系统的设计与实现

目录 一.前言介绍 二、主要技术 2.1 SSM框架介绍 2.2 MYSQL数据库 2.3 持久层框架MyBatis 2.4 前端框架BootStrap 三. 系统设计 3.1 系统架构设计 3.2 系统功能模块 3.2.1 学生模块 3.2.2 教师模块 3.2.3 管理员模块 四、数据库设计 4.1 数据分析 4.2 概念设计 …...

一招鉴别真假ChatGPT,并简要介绍ChatGPT、GPT、GPT2和GPT3模型之间的区别和联系

以下内容除红色字体部分之外,其他均来源于ChatGPT自动撰写。 ChatGPT是基于GPT模型的对话生成模型,旨在通过对话模拟实现自然语言交互。它是为了改善人机对话体验而设计的,主要应用于聊天机器人、智能客服等场景。 与GPT模型相比,…...

华为OD机试 - 特异性双端队列(JS)

特异性双端队列 题目 有一个特异性的双端队列,该队列可以从头部到尾部添加数据,但是只能从头部移除数据。 小A一次执行 2n 个指令往队列中添加数据和移除数据, 其中 n 个指令是添加数据(可能从头部也可以从尾部添加) 依次添加 1 到 n , n 个指令是移出数据 现在要求移除数…...

Nginx自动封禁可疑Ip

文章目录一、Nginx封禁ip1、简介2、nignx 禁止IP访问2.1 方法一2.2 方法二3、关于 deny 的使用二、脚本自动封禁Ip1、流程介绍2、脚本实战2.1 核心脚本解释2.2 编写shell脚本2.3 crontab定时一、Nginx封禁ip 1、简介 在网站维护过程中,有时候我们需要对一些IP地址…...

分布式事务--理论基础

1、事务基础 1.1、什么是事务 事务可以看做是一次大的活动,它由不同的小活动组成,这些活动要么全部成功,要么全部失败。 1.2、本地事务 在同一个进程内,控制同一数据源的事务,称为本地事务。例如数据库事务。 在计…...

Matlab数学建模常用算法及论文插图绘制模板资源合集

最近有很多朋友咨询我关于Matlab论文插图绘制方面的问题。 问了一下,这些朋友中,除了写博士论文的,大部分都是要参加美赛的。 这让我突然想起,自己曾经为了水论文,购买过一批Matlab数学建模的资料。 想了想&#xf…...

C语言【动态内存管理 后篇】

动态内存管理 后篇🫅经典例题🤦‍♂️题目1🤦‍♂️题目2🤦‍♂️题目3🤦‍♂️题目4🫅C/C程序的内存开辟前面的一篇文章动态内存管理 前篇,我们已经了解过了动态内存管理的相关信息&#xff0c…...

四大步骤,教你彻底关闭Win10自动更新

文章目录一、禁用Windows Update服务二、在组策略里关闭Win10自动更新相关服务三、禁用任务计划里边的Win10自动更新四、在注册表中关闭Win10自动更新参考资料一、禁用Windows Update服务 1、同时按下键盘 Win R,打开运行对话框,然后输入命令 services…...

通信算法之一百零四:QPSK完整收发仿真链路

1.发射机物理层基带仿真链路 1.1 % Generates the data to be transmitted [transmittedBin, ~] BitGenerator(); 2.2 % Modulates the bits into QPSK symbols modulatedData QPSKModulator(transmittedBin); 2.3 % Square root Raised Cosine Transmit Filter %comm…...

时间复杂度(超详解+例题)

全文目录引言如何衡量一个算法的好坏时间复杂度时间复杂度的定义时间复杂度的大O表示法实例test1test2test3test4test5总结引言 如何衡量一个算法的好坏 我们在写算法的时候,对于实现同样的作用的不同算法,我们如何判断这个算法的好坏呢? …...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

JVM垃圾回收机制全解析

Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

AspectJ 在 Android 中的完整使用指南

一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中&#xff0…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行

项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看

文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向

在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...