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

静态工厂和构造器有个共同的的局限性:它们都不能很好的扩展大量可选的参数。举个栗子:一个饮料类,不同的饮料,包含相同的参数(水,卡路里,脂肪量,糖量……),也有不同的参数,有的含有矿物质,胡萝卜素 , 钠 , XX酸……对于这样的类,我们该如何设计构造器?

传统构造器的困境


对于传统的构造器,有多少个值域就要去写多少个构造器。比如饮料公共参数是8个,那么至少要写8个构造器出来,可选参数从1个到8个。

public class Beverage{
    private final double volume;//单位:ml
    private final double calories;//卡路里
    private final double fat;//脂肪
    private final double sugar;//糖
    private final double sodium;//钠
    private final double Protein;//蛋白质

    public Beverage(double volume){
       this.volume = volume;
    }

    public Beverage(double volume,double calories){
       this.volume = volume;
       this.calories = calories;
    }

    public Beverage(double volume,double calories,double fat){
       this.volume = volume;
       this.calories = calories;
       this.fat = fat;
    }
    .
    .
    .
    .
    .
    .    
 }

随着参数的增加,这样的构造器将随着增加。然而按照上面的构造器,有些构造器的参数,获取用不到。比如无糖可乐,它没有suger这个参数,有其他5个参数。那么你需要选用参数最多参数的那个构造器,并且设置suger=0.或许读者会说那就去掉suger,用其他5个参数,在写一个构造器。不是没往这方面想过,你可以试想一下,6个参数,创造所有类型的构造器的个数,是6个阶乘。随着参数个数增加,这个数目是很可怕的,想想要写那么多构造器,都觉得这简直要命啊。说好的优雅呢?


JavaBeans模式

在这种模式下,我们只需要调用一个默认无参的构造器来创建对象,然后通过 setter 方法来设置必要参数或者可选参数的值:

public class Beverage{
    private final double volume = 0;//单位:ml
    private final double calories = 0;//卡路里
    private final double fat = 0;//脂肪
    private final double sugar = 0;//糖
    private final double sodium = 0;//钠
    private final double Protein = 0;//蛋白质

    public void setVolume(double volume){this.volume = volume;}
    public void setCalories(double calories){this.calories= calories;}
    public void setFat (double fat ){this.fat = fat ;}
    public void setSugar (double sugar ){this.sugar = sugar ;}
    public void setSodium (double sodium ){this.sodium = sodium ;}
    public void setProtein (double Protein ){this.Protein = Protein ;}
 }

用JavaBeans 模式创建实例非常方便,代码易读性很高:

Beverage beverage = new Beverage();  //----------------->N
beverage.setVolume(500); //----------------->A
beverage.setCalories(0); //----------------->B
beverage.setFat(50);     //----------------->C
beverage.setSugar (50);  //----------------->D  

但是JavaBeans模式有个缺点,它阻止了吧类做成不可变的的可能。简单的说,就是,javaBeans模式下,类的实例,对象很容易就被改变。如上面的5行代码,N :通过调用无参构造器,生成了一个空对象beverage。但是从N开始到D,对象beverage 一直在变化。每一次调用setter 方法,就是在改变beverage 的值域。你无法保证,在D操作之后没有人再去调用setter方法,所以要保证我们一直操作的是D操作后的 beverage,我们要给对象beverage “上锁”,这一段时间,谁也不要来动我的beverage。这就需要我们付出额外的工作来保证对象beverage 的一致性。


有没有办法,保持创建对象可读性高,简单同时还保证 对象的一致性呢?方法是有的,那就是Builder模式。它不直接生成想要的对象,而是通过调用构造器,得到一个builder对象,然后由builder对象调用类似setter方法来设置可选参数。最后,通过无参的build方法生成一个不可变对象,而Builder 类 是我们设计类的静态成员类。

public class Human {
  //三个必要参数
  private final String name;
  private final int age ;
  private final String sex ;

  //四个可选参数
  private final String country;
  private final double height;
  private final double weight;
  private final String bloodType;

  //静态成员类--Builder
  public static class Builder{
    private final String name;
    private final int age ;
    private final String sex ;

    private String country = "";
    private double height = 0.0;
    private double weight = 0.0;
    private String bloodType = "A";


    public Builder(String name,int age,String sex){
      this.name = name;
      this.age = age;
      this.sex = sex; 
    }

    public Builder country(String countryName){
      country = countryName;
      return this;
    }    
    public Builder height(double heightVal){
      height = heightVal;
      return this;
    }    
    public Builder weight(double weightVal){
      weight = weightVal;
      return this;
    }    
    public Builder bloodType(String bloodTypeVal){
      bloodType = bloodTypeVal;
      return this;
    }

    public Human build(){
      return new Human(this);
    }
  }
 //通过builder来给对象human设置参数
  private Human(Builder builder){
    name = builder.name;
    age = builder.age;
    sex = builder.sex;

    country = builder.country;
    height = builder.height;
    weight = builder.weight;
    bloodType = builder.bloodType;
  }


  public String getName() {
    return name;
  }
  public int getAge() {
    return age;
  }
  public String getSex() {
    return sex;
  }
  public String getCountry() {
    return country;
  }
  public double getHeight() {
    return height;
  }
  public double getWeight() {
    return weight;
  }
  public String getBloodType() {
    return bloodType;
  }

  @Override
  public String toString() {
    StringBuffer str = new StringBuffer();
    str.append(" name="+name.toString());
    str.append(",age="+String.valueOf(age));
    str.append(",sex="+String.valueOf(sex));
    str.append(",country="+String.valueOf(country));
    str.append(",height="+String.valueOf(height));
    str.append(",weight="+String.valueOf(weight));
    str.append(",bloodType="+String.valueOf(bloodType));
    return super.toString()+str.toString();
  }

  public static void main(String[] args) {
    Human chinese = new Human.Builder("David", 25, "man").bloodType("A").country("China").build();
    System.out.println(chinese.toString());
    chinese.country.toString();
  }

}

如上,类Human 值域是final,所以我们无法在外部像JavaBeans模式调用setter来给对象设置参数,也没有传统的构造器,这样就保证了我们之前所说的一致性。

Human chinese = new Human.Builder("David",25,"man").bloodType("A").country("China").build();

我们通过调用静态成员类Builder的构造器,给builder设置了三个必要参数,然后调用方法设置可选参数,方法名与参数名一致这样代码的可读性就高了,最后通过调用Builder的无参构造器去创建Human 的对象,并把相关参数传递到chinese中,这样就完成了。随后的测试中可发现,这个chinese 是无法改变的。在满足高可读性,方便性,一致性中,Builder模式为我们给出来选择。


是不是一定要用Builder模式,这就要根据实际场景来了。非必要,JavaBeans是第一选择,正如在工作中,大多数时候使用的就是JavaBeans模式,如遇到对对象一致性要求高时,那就愉快用Builder模式吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值