`
liwenshui322
  • 浏览: 520713 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

(转)第2条:遇到多个构造器参数时要考虑用构建器

 
阅读更多

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

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

 

// Telescoping constructor pattern - does not scale well!

public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // optional
private final int fat; // (g) optional
private final int sodium; // (mg) optional
private final int carbohydrate; // (g) optional

public 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个参数,看起来还不算太糟,问题是随着参数数目的增加,它很快就失去了控制。

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

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

 

// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1; 
// Required; no default value
private int servings = -1; 
// " " " "
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

public NutritionFacts() { }

// Setters
public 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; }

}

      这种模式不具备telescoping constructor模式的任何缺点。说得明白一点,就是创建实例很容易,这样产生的代码读起来也很容易:

 

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

 

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

     当对象的构造完成,并且不允许在解冻之前使用时,通过手工"冻结"对象,可以弥补这些不足,但是这种方式十分笨拙,在实践中很少使用。此外,它甚至会在运行时导致错误,因为编译器无法确保程序员会在使用之前先在对象上调用freeze方法。

 

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

 

// Builder Pattern
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 carbohydrate = 0;
private int sodium = 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 carbohydrate(int val){ 
carbohydrate = val; return this; 
}

public Builder sodium(int val){
 sodium = 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本身,以便可以把调用链接起来。下面就是客户端代码:

 

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

 

     这样的客户端代码很容易编写,更为重要的是,易于阅读。builder模式模拟了具名的可选参数,就像Ada和Python中的一样。

    

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

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

 

  附:原文地址:http://book.51cto.com/art/200901/106048.htm


分享到:
评论

相关推荐

    effective Java(第3版)各章节的中英文学习参考(已完成).zip

    2 章 创建和销毁对象(创建和气氛对象)第二章简介(章节介绍)第 1 条考虑静态工厂方法而不是构造函数(考虑以静态工厂方法代替构造函数)Item 2: 当面对许多构造函数参数时考虑构建器(在面对多个构造函数参数时,...

    词法分析器构造器

    词法分析器构造器是编译器设计领域中的一个重要组成部分,它主要负责将源代码文本分解成一个个有意义的符号或“标记”(Token),为后续的语法分析阶段提供输入。在编程语言处理过程中,词法分析是第一步,它将源...

    《C++20设计模式》学习笔记-第2章构造器模式学习代码

    第2章着重讲解了构造器模式,这是一种创建型设计模式,它关注的是对象的初始化过程。下面将详细阐述构造器模式的相关知识点,并结合提供的文件名来推测可能的学习内容。 首先,让我们理解构造器模式的基本概念。在...

    Effective-Java读书笔记(上)

    **遇到多个构造器参数时的考虑**: - **重叠构造器模式**:这种模式下,每个构造器提供比前一个多一个参数,但是这种模式会导致构造器过多,增加复杂度。 - **JavaBeans模式**:虽然使用无参构造器并结合setter方法...

    桂浩 解释器构造实验2

    解释器的构造涉及到多个关键步骤,包括词法分析、语法分析、语义分析和代码生成。下面我们将详细探讨这些步骤以及它们在实验中的重要性。 1. **词法分析**:这是解释器的第一步,它将源代码分解成一系列称为“标记...

    编译原理及实践手工构造词法分析器

    在编程领域,编译原理是理解计算机语言转换...此外,了解词法分析器的构造也有助于将来在阅读和理解编译器源代码时更加得心应手,对于想要深入研究编译技术或者从事相关开发工作的人来说,这是一个非常宝贵的实践机会。

    Thinkphp5第六讲:数据库操作之查询构造器

    在ThinkPHP5框架中,查询构造器是一种强大的工具,它基于PDO实现,旨在简化数据库操作,提供统一的语法,无论你使用的是MySQL、Oracle还是SQL Server等不同的数据库系统。查询构造器的主要优点在于其语法的一致性和...

    编译原理词法分析器的构造

    词法分析器的构造是编译器设计与实现的重要组成部分,涉及到了状态转移、正则表达式等多个概念。 1. **词法分析器的作用** 词法分析器的作用是将源代码中的字符流转化为符号流,这个过程包括识别关键字、标识符、...

    一个直线型语言的解释器(二):解释器后端——解释器模式的应用

    2. **非终端元素**(Non-Terminal Elements):这些是更复杂的语言构造,由一个或多个其他元素组成。它们代表了语言的语法结构,如表达式或控制结构。 3. **表达式类**(Expression Class):这是所有元素的基类,...

    用LL(1)方法构造语法分析器

    如果是,则转到第二步。 2. 生成LL(1)分析表:如果输入的文法为LL(1)文法,我们可以使用LL(1)方法生成分析表。分析表是一个二维数组,用于存放各个符号的FIRST和FOLLOW集合。 3. 判断输入串是否为给定文法的...

    编译程序构造

    在这个过程中,编译器分为多个阶段,主要包括词法分析、语法分析、语义分析和代码生成。 词法分析是编译程序的第一个阶段,它的任务是将源代码分解成一个个称为“token”的基本单元,这些token可以是关键字、标识符...

    Keras 中构建神经网络模型的 5 个步骤1

    或者,我们可以预先定义好层的列表,然后一次性传递给Sequential构造器: ```python layers = [Dense(2)] model = Sequential(layers) ``` 对于输入层,我们需要指定输入维度,这可以通过`input_dim`属性完成。...

    CLR.via.C#.(中文第3版)(自制详细书签)Part2

    第2章 生成、打包、部署和管理应用程序及类型 2.1 .NET Framework部署目标 2.2 将类型生成到模块中 2.2.1 响应文件 2.3 元数据概述 2.4 将模块合并成程序集 2.4.1 使用Visual Studio IDE将程序集添加到项目中...

    ll1语法分析器

    每个单元格中包含一个或多个产生式,表示当前非终结符遇到特定输入符号时应执行的动作。如果一个非终结符在某个输入符号下有且只有一个产生式,那么这个分析器就是LL(1),"1"表示只看一个输入符号就能决定下一步行动...

    编译原理:LR分析程序

    在这个压缩包中,"LR.C"文件很可能是一个用C语言编写的LR分析器的源代码。为了理解和分析这个程序,我们需要具备以下基础知识: 1. **语法分析**:这是编译过程的第二阶段,即词法分析后的下一步。LR分析属于自底...

    Laravel开发-former 强大的表单构造器

    在 Laravel 中,开发者可以利用其丰富的工具和库来提高开发效率,而 "Former" 正是这样一个专为 Laravel 设计的表单构造器。 Former 是一款强大的第三方库,它的主要目标是帮助 Laravel 开发者快速、方便地创建和...

    PLL从入门到应用设计

    #### 第二十四章:使用差分相位检测器输出的主动环路滤波器 - **内容介绍:**介绍了如何利用差分相位检测器的输出来设计主动环路滤波器。 - **核心知识点:** - 输出处理:如何处理差分相位检测器的输出信号。 - ...

    计算机编译原理编译程序构造实践第二版(张幸儿)

    《计算机编译原理编译程序构造实践第二版》是由张幸儿编著的一部深入探讨编译技术的著作。该书结合丰富的实例,为读者提供了实际动手构建编译器的宝贵经验,旨在帮助读者将理论知识转化为实际操作能力,避免学习后...

Global site tag (gtag.js) - Google Analytics