`
liwenshui322
  • 浏览: 511805 次
  • 性别: 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


分享到:
评论

相关推荐

    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将程序集添加到项目中...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    7.3.3 让方法有多个参数 163 7.4 返回值:汽车超速了吗? 164 7.4.1 写一个有返回值的方法 164 7.4.2 调用有返回值的方法 165 7.4.3 发生了什么?如何使用方法的返回值? 166 7.4.4 使用return结束方法 166 ...

    LuaBind 源码 (Lua增强库)

    resume_fucntion()调用的返回值是 lua_yield()的第一个传入参数.当你想要继续一个 协程的时候,你只需要调用 resume() 在你的 lua_State() 上,因为它已经在执行一个函数 (即先前出入的入口函数),所以你不需要再次传入...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    7.3.3 让方法有多个参数 163 7.4 返回值:汽车超速了吗? 164 7.4.1 写一个有返回值的方法 164 7.4.2 调用有返回值的方法 165 7.4.3 发生了什么?如何使用方法的返回值? 166 7.4.4 使用return结束方法 166 ...

    Spring面试题

    在对由三部分组成的 Spring 系列 的第 1 部分进行总结时,我使用了一个示例,演示了如何通过 Spring IOC 容器注入应用程序的依赖关系(而不是将它们构建进来)。 我用开启在线信用帐户的用例作为起点。对于该实现,...

    超级有影响力霸气的Java面试题大全文档

    但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地...

    C#5.0本质论第四版(因文件较大传的是百度网盘地址)

    5.7.5 构造器链:使用this调用另一个构造器 168 5.8 静态成员 172 5.8.1 静态字段 172 5.8.2 静态方法 174 5.8.3 静态构造器 175 5.8.4 静态属性 176 5.8.5 静态类 177 5.9 扩展方法 ...

    【05-面向对象(下)】

    我们把该类的构造器使用Private修饰,从而把该 类的所有构造器隐藏起来。  –2.则需要提供一个public方法作为该类的访问点,用于创建该类的对象,且必须使用static修饰  –3.该类还必须缓存已经创建的对象,必须...

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

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

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

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

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

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

    C#本质论(第3版)

    5.7.5 使用this调用另一个构造器 5.8 静态成员 5.8.1 静态字段 5.8.2 静态方法 5.8.3 静态构造器 5.8.4 静态属性 5.8.5 静态类 5.9 扩展方法 5.10 封装数据 5.10.1 const 5.10.2 readonly 5.11 嵌套类 ...

    effectiveJava:有效的java se 书中的例子

    第 2 条:当面临许多构造函数参数时考虑构建器 伸缩构造函数模式有效,但是当参数很多时很难编写客户端代码,而且更难阅读 Builder 模式模拟命名的可选参数 Class.newInstance 破坏编译时异常检查 在设计具有构造...

    construct:Java版本的“构造”是一个python库,用于解析和构建数据结构(二进制或文本)

    构造具有位和字节的粒度,对称操作(解析和构建),面向组件的声明性设计,易于调试和测试,易于扩展的子类系统以及许多原始数据使您的工作更轻松的结构: 领域结构工会中继器元构造开关按需解析指针和更多!...

    python cookbook(第3版)

    8.16 在类中定义多个构造器 8.17 创建不调用init方法的实例 8.18 利用Mixins扩展类功能 8.19 实现状态对象或者状态机 8.20 通过字符串调用对象方法 8.21 实现访问者模式 8.22 不用递归实现访问者模式 8.23 ...

    3D游戏卷2:动画与高级实时渲染技术——1

    第2章 高级游戏系统剖析Ⅱ:实时处理 2.1 视见和BSP 2.1.1 生成视见约束体的面 2.1.2 远近裁剪面和视见约束体 2.2 照相机控制 2.3 使用BSP的基本碰撞检测和反弹 2.3.1 碰撞和BSP遍历 2.3.2 粒子,场景检测和反弹 2.4...

    java 面试题 总结

    但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地...

    JAVA面试题最全集

    一个“.java”原文件中是否可以包括多个类(不是内部类)? 53.掌握内部类和接口的概念 54.StringTokenizer类的使用 55.数据结构,如何遍历List中的元素? 如果要按照键值保存或者访问数据,使用什么数据结构? ...

    Visual C#2010 从入门到精通(Visual.C#.2010.Step.By.Step).完整去密码锁定版 I部分

    第2章 使用变量、操作符和表达式 25 2.1 理解语句 25 2.2 使用标识符 26 2.3 使用变量 27 2.3.1 命名变量 27 2.3.2 声明变量 28 2.4 使用基本数据类型 28 2.4.1 未赋值的局部变量 29 2.4.2 显示基本数据类型...

    3D游戏卷2:动画与高级实时渲染技术——2

    第2章 高级游戏系统剖析Ⅱ:实时处理 2.1 视见和BSP 2.1.1 生成视见约束体的面 2.1.2 远近裁剪面和视见约束体 2.2 照相机控制 2.3 使用BSP的基本碰撞检测和反弹 2.3.1 碰撞和BSP遍历 2.3.2 粒子,场景检测和反弹 2.4...

Global site tag (gtag.js) - Google Analytics