Builder内部类

今天在看当当网的开源项目分布式任务调度框架源码的时候,感到很奇怪,代码如下

@AllArgsConstructor(access = AccessLevel.PRIVATE)
public final class LiteJobConfiguration implements JobRootConfiguration {

    private final JobTypeConfiguration typeConfig;

    private final boolean monitorExecution;

    private final int maxTimeDiffSeconds;

    private final int monitorPort;

    private final String jobShardingStrategyClass;

    private final int reconcileIntervalMinutes;

    private final boolean disabled;

    private final boolean overwrite;

    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    public static class Builder {

        private final JobTypeConfiguration jobConfig;

        private boolean monitorExecution = true;

        private int maxTimeDiffSeconds = -1;

        private int monitorPort = -1;

        private String jobShardingStrategyClass = "";

        private boolean disabled;

        private boolean overwrite;

        private int reconcileIntervalMinutes = 10;

        /*
        此处省略各属性的this setter调用链
        */
        public final LiteJobConfiguration build() {
            return new LiteJobConfiguration(jobConfig, monitorExecution, maxTimeDiffSeconds, monitorPort, jobShardingStrategyClass, reconcileIntervalMinutes, disabled, overwrite);
        }
    }
}

可以看到这段代码有以下特征
1. 外部类有非常多的属性,外部类有的属性内部类都有,并且外部类的属性是final类型的
2. 外部类的构造方法是私有的
3. 内部类有一个build方法用于实例化LiteJobConfiguration对象

这样设计类的好处如下
1. 避免恼人的不必要参数设置
2. 利用set设置不可变属性

先看看我们在不使用builder内部类的时候如何实例化这样的对象

有如下这样的饮料bean,可以代表咖啡,可乐或者果汁,它包含的属性中energy和carbohydrate必须要被设置,其它属性选择设置

public class NutritionFact {
   private final int energy;
   private final int carbohydrate;
   private final int protein;
   private final int sodium;
   private final int fat;
   private final int vitaminC;

   public NutritionFact(int energy, int carbohydrate, int protein, int sodium, int fat,int vitaminC) {
       this.energy = energy;
       this.carbohydrate = carbohydrate;
       this.protein = protein;
       this.sodium = sodium;
       this.fat = fat;
       this.vitaminC = vitaminC;
   }
   //getters ...
}

像上面这样的类我们需要进行如下这样的初始化

NutritionFact colaNutritionFact = new NutritionFact(1, 2, 0, 5, 0, 0);
System.out.println(colaNutritionFact);

可以看到虽然我们只要设置energy,carbohydrate,sodium属性,但是我们还是得在构造器中设置每个参数,其中不需要设置的参数必须设置为0
有人可能会说,可以利用构造函数重载

public NutritionFact(int energy, int carbohydrate, int protein) {
        this.energy = energy;
        this.carbohydrate = carbohydrate;
        this.protein = protein;
        this.sodium=0;
        this.fat=0;
        this.vitaminC=0;
    }

但是这没有解决参数0设置问题,只不过将调用该类的使用者设置0参数转化为开发该类的开发者设置

还有人会说,我们可以设置如下构造函数

public NutritionFact(int energy, int carbohydrate) {
        this.energy = energy;
        this.carbohydrate = carbohydrate;
}

先利用构造函数设置必填参数,然后利用setter选填设置参数,但是这个类所有参数都是final类型的,必须在实例化对象时就设置所有参数

我们利用当当网那样的设计来解决这个问题

public class NewNutritionFact {
    /*
    可以根据需要将这些属性全部设置为final类型
     */
    private final int energy;
    private final int carbohydrate;
    private final int protein;
    private final int sodium;
    private final int fat;
    private final int vitaminC;

    public NewNutritionFact(NutritionFactBuilder builder) {
        this.energy = builder.energy;
        this.carbohydrate = builder.carbohydrate;
        this.protein = builder.protein;
        this.sodium = builder.sodium;
        this.fat = builder.fat;
        this.vitaminC = builder.vitaminC;
    }

    /*
    public static类型让外部可以访问
     */
    public static class NutritionFactBuilder {
        /*
        以下两个是必须属性
         */
        private int energy;
        private int carbohydrate;
        /*
        以下四个是可选属性
         */
        private int protein;
        private int sodium;
        private int fat;
        private int vitaminC;

        /*
        必须要设置的两个参数
         */
        public NutritionFactBuilder(int energy, int carbohydrate) {
            this.energy = energy;
            this.carbohydrate = carbohydrate;
        }

        /**
         * 链式调用
         *
         * @param protein
         * @return
         */
        public NutritionFactBuilder setProtein(int protein) {
            this.protein = protein;
            return this;
        }

        public NutritionFactBuilder setSodium(int sodium) {
            this.sodium = sodium;
            return this;
        }

        public NutritionFactBuilder setFat(int fat) {
            this.fat = fat;
            return this;
        }

        public NutritionFactBuilder setVitaminC(int vitaminC) {
            this.vitaminC = vitaminC;
            return this;
        }

        public NewNutritionFact build() {
            return new NewNutritionFact(this);
        }

    }

}

实例化过程如下

public void innerBuilder() {
    NewNutritionFact colNutritionFact = new NewNutritionFact.NutritionFactBuilder(255, 12).setSodium(5).build();
    System.out.println(colNutritionFact);
    NewNutritionFact coffeeNutritionFact = new NewNutritionFact.NutritionFactBuilder(255, 11).setFat(1).build();
    System.out.println(coffeeNutritionFact);
}

配合上调用链代码非常的简洁,

总结一下,如果需要设计一个拥有很多可选属性类的时候,特别是其为不可变类的时候使用内部类Builder是个不错的选择

你可能感兴趣的:(设计模式)