zoukankan      html  css  js  c++  java
  • 第2条:遇到多个构造器参数时要考虑用构建器

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

      思考:考虑用一个类表示包装食品外显示的营养成分标签。这些标签有几个域是必须的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。大多数产品的某几个可选域中都会有几个非零的值。

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

     1 public class NutritionFacts {
     2     private final int servingSize;        //(ml)                required
     3     private final int servings;            //(per container)    required
     4     private final int calories;            //                    optional
     5     private final int fat;                //(g)                optional
     6     private final int sodium;            //(mg)                optional
     7     private final int carbohydrate;        //(g)                optional
     8     public NutritionFacts(int servingsize, int servings) {
     9         this(servingsize,servings,0);
    10     }
    11     public NutritionFacts(int servingsize, int servings, int calories) {
    12         this(servingsize,servings,calories,0);
    13     }
    14     public NutritionFacts(int servingsize, int servings, int calories, int fat) {
    15         this(servingsize,servings,calories,fat,0);
    16     }
    17     public NutritionFacts(int servingsize, int servings, int calories, int fat, int sodium) {
    18         this(servingsize,servings,calories,fat,sodium,0);
    19     }
    20     public NutritionFacts(int servingsize, int servings, int calories, int fat, int sodium, int carbohydrate) {
    21         super();
    22         this.servingSize = servingsize;
    23         this.servings = servings;
    24         this.calories = calories;
    25         this.fat = fat;
    26         this.sodium = sodium;
    27         this.carbohydrate = carbohydrate;
    28     }
    29 }

    方法的弊端:这个构造器通常需要许多你本不想设置的参数,但还是不得不为它们传递值。并且随着参数数目的增加,难以控制。

    方法的总结:重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。用户无法快速明确的知道参数代表的意思。一串类型相同的参数,输入过程中出错,编译器不会出错,但是程序运行时会出现错误的行为。

    第二种实现方法:JavaBeans模式,在这种模式下,调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个可选的相关参数:

     1 public class NutritionFacts {
     2     private int servingSize = -1;
     3     private int servings = -1;
     4     private int calories = 0;
     5     private int fat = 0;
     6     private int sodium = 0;
     7     private int carbohydrate = 0;
     8     public NutritionFacts() {}
     9     //Setters
    10     public void setServingSize(int servingSize) {
    11         this.servingSize = servingSize;
    12     }
    13     public void setServings(int servings) {
    14         this.servings = servings;
    15     }
    16     public void setCalories(int calories) {
    17         this.calories = calories;
    18     }
    19     public void setFat(int fat) {
    20         this.fat = fat;
    21     }
    22     public void setSodium(int sodium) {
    23         this.sodium = sodium;
    24     }
    25     public void setCarbohydrate(int carbohydrate) {
    26         this.carbohydrate = carbohydrate;
    27     };
    28 }

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

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

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

    第三种实现方法:Builder模式的一种形式。不直接生成想要的对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类。下面就是示例:

     1 private final int servingSize;        //(ml)                required
     2     private final int servings;            //(per container)    required
     3     private final int calories;            //                    optional
     4     private final int fat;                //(g)                optional
     5     private final int sodium;            //(mg)                optional
     6     private final int carbohydrate;        //(g)                optional
     7     
     8     public static class Builder{
     9         //Required parameters
    10         private final int servingSize;
    11         private final int servings;
    12         //optional parameters - initialized to default values
    13         private int calories = 0;
    14         private int fat = 0;
    15         private int sodium = 0;
    16         private int carbohydrate = 0;
    17         
    18         public Builder(int servingSize,int servings) {
    19             this.servingSize = servingSize;
    20             this.servings = servings;
    21         }
    22         
    23         public Builder calories(int val) {
    24             calories = val;
    25             return this;
    26         }
    27         public Builder fat(int val) {
    28             fat = val;
    29             return this;
    30         }
    31         public Builder carbohydrate(int val) {
    32             carbohydrate = val;
    33             return this;
    34         }
    35         public Builder sodium(int val) {
    36             sodium = val;
    37             return this;
    38         }
    39         
    40         public NutritionFacts build() {
    41             return new NutritionFacts(this);
    42         }
    43     }
    44     
    45     private NutritionFacts(Builder builder) {
    46         servingSize = builder.servingSize;
    47         servings = builder.servings;
    48         calories = builder.calories;
    49         fat = builder.fat;
    50         sodium = builder.sodium;
    51         carbohydrate = builder.carbohydrate;
    52         
    53     }

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

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

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

  • 相关阅读:
    Jenkins+postman+Newman之API自动化测试
    python之路——迭代器和生成器
    文件操作
    函数
    python基础数据类型二
    dp的一种理解角度及[NOI2020]命运 题解
    从零开始“发明”Treap
    博客停更
    [转]Linux下C语言-RPC远程调用编程rpcgen用法
    一位编程小白的自述 —— 从小学到现在
  • 原文地址:https://www.cnblogs.com/remote/p/10051172.html
Copyright © 2011-2022 走看看