effective.java.2 当构造方法参数过多时使用builder模式

通常情况下,构造方法的调用需要许多你不想设置的参数,但是你不得不为它们传递一个值。
比如:

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);

上边代码第四个参数传了一个默认值0,其实这个值没必要设置。这样很难编写客户端代码,而且很难读懂它。可以考虑使用下面的方式

// 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 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 cocaCola = new NutritionFacts.Builder(240, 8)
 .calories(100).sodium(35).carbohydrate(27).build();

JavaBeans 模式本身有严重的缺陷。由于构造方法在多次调用中被分割,所以在构造过程中 JavaBean可能处于不一致的状态。该类没有通过检查构造参数参数的有效性来执行一致性的选项。在不一致的状态下尝试使用对象可能会导致与包含 bug 的代码大相径庭的错误,因此很难调试。一个相关的缺点是,JavaBeans 模式排除了让类不可变的可能性,并且需要增加工作确保线程安全。

抽象类中也可以有builder

package com.effective.java.paragraph2;

import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
/**
 * Builder模式非常适合类层次结构。使用平行层次结构的builder,每次嵌套在相应的类中。抽象类有抽象类的builder;
 * 具体类有具体类的builder。例如,代表各种pizza的根层次机构的抽象类Pizza
 *
 */
public abstract class Pizza {

	public enum Topping{HAM,MUSHROOM,ONION,PRPPER,SAUSAGE};
	final Set<Topping> toppings;
	//Pizza.Builder是有一个带有递归类型参数的泛型类型。这与抽象的self方法一起,允许方法链在子类中正常工作,
	//而不需要强制转换。java缺乏自我类型的这种变通解决办法称为模拟自我类型的习惯用法
	abstract static class Builder<T extends Builder<T>>{
		EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
		
		public T addTopping(Topping topping){
			toppings.add(Objects.requireNonNull(topping));
			return self();
		}
		abstract Pizza build();
		protected abstract T self();
	}
	Pizza (Builder<?> builder){
		toppings = builder.toppings.clone();
	}
}

Pizza的子类

package com.effective.java.paragraph2;

import java.util.Objects;

public class NyPizza extends Pizza{
	public enum Size{SMALL,MEDITUM,LARGE};
	private final Size size;

	public static class Builder extends Pizza.Builder<Builder>{
		private final Size size;
		public Builder (Size size){
			this.size = Objects.requireNonNull(size);
		}
		@Override
		public NyPizza build(){
			//返回的是pizza的子类,这种技术,其一个子类的方法被声明为返回在超类中声明的返回类型的子类型
			//称为 协变返回类型
			return new NyPizza(this);
		}
		@Override
		protected Builder self() {
			return this;
		}
	}
	private NyPizza(Builder builder){
		super(builder);
		size = builder.size;
	}
	public Size getSize() {
		return size;
	}
}

客户端调用基本和简单的builder相同

package com.effective.java.paragraph2;

import com.google.gson.Gson;

public class PizzaApp {
	public static void main(String args[]){
		NyPizza pizza = new NyPizza.Builder(com.effective.java.paragraph2.NyPizza.Size.SMALL).
				addTopping(com.effective.java.paragraph2.Pizza.Topping.MUSHROOM).
				addTopping(com.effective.java.paragraph2.Pizza.Topping.SAUSAGE).build();
		System.out.println(new Gson().toJson(pizza));
		//输出:{"size":"SMALL","toppings":["MUSHROOM","SAUSAGE"]}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jwt_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值