博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用 builder 模式解决构造方法参数过多的情况
阅读量:3897 次
发布时间:2019-05-23

本文共 5108 字,大约阅读时间需要 17 分钟。

静态工厂和构造方法都有一个限制:它们不能很好地扩展到很多可选参数的情景。请考虑一个代表包装食品上的营养成分标签的例子。这些标签有几个必需的属性——每次建议的摄入量,每罐的份量和每份卡路里 ,以及超过 20个可选的属性——总脂肪、饱和脂肪、反式脂肪、胆固醇、钠等等。大多数产品都有非零值,只有少数几个可选属性。应该为这样的类编写什么样的构造方法或静态工厂?传统上,程序员使用了可伸缩(telescoping constructor)构造方法模式,在这种模式中,只提供了一个只所需参数的构造函数,另一个只有一个可选参数,第三个有两个可选参数,等等,最终在构造函数中包含所有可选参数。这就是它在实践中的样子。为了简便起见,只显示了四个可选属性:

public class NutritionFacts {
private final int servingSize; // (mL) requiredprivate final int servings; // (per container) requiredprivate final int calories; // (per serving) optionalprivate final int fat; // (g/serving) optionalprivate final int sodium; // (mg/serving) optionalprivate final int carbohydrate; // (g/serving) 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 值。「只有」六个参数可能看起来并不那么糟糕,但随着参数数量的增加,它会很快失控。

简而言之,可伸缩构造方法模式是有效的,但是当有很多参数时,很难编写客户端代码,而且很难读懂它。读者不知道这些值是什么意思,并且必须仔细地计算参数才能找到答案。一长串相同类型的参数可能会导致一些细微的bug。如果客户端意外地反转了两个这样的参数,编译器并不会抱怨,但是程序在运行时会出现错误行为
当在构造方法中遇到许多可选参数时,另一种选择是 JavaBeans 模式,在这种模式中,调用一个无参数的构造函数来创建对象,然后调用 setter 方法来设置每个必需的参数和可选参数:

public class NutritionFacts {
// Parameters initialized to default values (if any)private int servingSize = -1; // Required; no default valueprivate int servings = -1; // Required; no default valueprivate int calories = 0;private int fat = 0;private int sodium = 0;private int carbohydrate = 0;public NutritionFacts() {
}// Setterspublic void setServingSize(int val) {
servingSize = val; }public void setServings(int val) {
servings = val; }public void setCalories(int val) {
calories = val; }public void setFat(int val) {
fat = val; }public void setSodium(int val) {
sodium = val; }public void setCarbohydrate(int val) {
carbohydrate = val; }}

这种模式没有伸缩构造方法模式的缺点。有点冗长,但创建实例很容易,并且易于阅读所生成的代码:

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

不幸的是,JavaBeans 模式本身有严重的缺陷。由于构造方法在多次调用中被分割,所以在构造过程中

JavaBean 可能处于不一致的状态。该类没有通过检查构造参数参数的有效性来执行一致性的选项。在不一致的状态下尝试使用对象可能会导致与包含 bug 的代码大相径庭的错误,因此很难调试。一个相关的缺点是,JavaBeans 模式排除了让类不可变的可能性,并且需要在程序员的部分增加工作以确保线程安全。
通过在对象构建完成时手动「冻结」对象,并且不允许它在解冻之前使用,可以减少这些缺点,但是这种变体在实践中很难使用并且很少使用。 而且,在运行时会导致错误,因为编译器无法确保程序员在使用对象之前调用freeze 方法。
幸运的是,还有第三种选择,它结合了可伸缩构造方法模式的安全性和 JavaBean 模式的可读性。 它是 Builder模式 的一种形式。客户端不直接调用所需的对象,而是调用构造方法 (或静态工厂),并使用所有必需的参数,并获得一个 builder 对象。然后,客户端调用 builder 对象的 setter 相似方法来设置每个可选参数。最后,客户端调用一个无参的 build 方法来生成对象,该对象通常是不可变的。Builder 通常是它所构建的类的一个静态成员类。以下是它在实践中的示例:

package com.yecc.effectivejava.builder;/** * Created by yecc on 2020/11/26 16:13 */public class NutritionFacts {
private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder {
// Required parameters private final int servingSize; private final int servings; // Optional parameters - initialized to default values private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public 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); } } private NutritionFacts(Builder builder) {
servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; }}

NutritionFacts 类是不可变的,所有的参数默认值都在一个地方。builder 的 setter 方法返回 builder 本身,这样调用就可以被链接起来,从而生成一个流畅的 API。下面是客户端代码的示例:

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

转载地址:http://bnyen.baihongyu.com/

你可能感兴趣的文章
关于yii2的一些知识的学习笔述
查看>>
用纯php实现MVC框架,文件目录模仿yii2
查看>>
新开发的体重管理项目----用纯php模仿yii2框架建立的
查看>>
JavaScript面向对象编程指南 的笔记
查看>>
在 2016 年做 PHP 开发是一种什么样的体验?(一)
查看>>
PHP获取客户端的IP
查看>>
从头开始学习yii2---1.搭建yii2开发环境
查看>>
从头开始学习yii2---3.语言包的配置
查看>>
yii2-表单验证的一些规则
查看>>
索引相关问题
查看>>
php面试可能会被问道的技术题汇总
查看>>
php面试题1-线程和进程的区别(顺带提下协程)
查看>>
php面试题2-用到过的传输协议
查看>>
php面试题3-yii2和yii的不一样的地方
查看>>
IOS 一些好的框架和 技术大牛的博客
查看>>
Java 和 Object-c的区别
查看>>
Windows环境下Android NDK环境搭建
查看>>
NDK Build 用法(NDK Build)
查看>>
Android NDK开发起步Hello Jni
查看>>
[已解决]AutoCompleteTextView 不显示匹配的内容,因为将空的内容添加进去了
查看>>