[关闭]
@Catyee 2017-07-14T01:25:05.000000Z 字数 2954 阅读 353

Effective Java 使用构建器Builder

java 经验之谈 高效开发


如果一个类有大量可选的参数,如果这个时候使用构造器就需要写大量有不同参数个数的构造器。举个例子,用一个类来表示包装食品外面显示的营养成分标签。这些标签中有一些是必须的,比如每份的含量,每罐的含量以及每份的卡路里。还有超过20多个可选的参数,比如总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。大多数的产品在某几个可选参数中都会有非零的值。

对于这样的类,传统方法是采用重叠构造器模式,在这种模式下,第一个构造器由所有必要参数来构造,第二个构造器多加一个可选参数,第三个多加两个可选参数,直到最后一个构造器包含所有可选参数。如下所示,为了简便,只显示四个可选域:

  1. /**
  2. * Telescoping constructor pattern - does not scale well
  3. */
  4. public class NutritionFacts {
  5. private final int servingSize; //(ml) required
  6. private final int servings; //(per container) required
  7. private final int calories; // optional
  8. private final int fat; //(g) optional
  9. private final int sodium; //(mg) optional
  10. private final int carbohydrate; //(g) optional
  11. public NutritionFacts(int servingSize, int servings) {
  12. this(servingSize, servings, 0);
  13. }
  14. public NutritionFacts(int servingSize, int servings, int calories) {
  15. this(servingSize, servings, calories, 0);
  16. }
  17. public NutritionFacts(int servingSize, int servings, int calories, int fat) {
  18. this(servingSize, servings, calories, fat, 0);
  19. }
  20. public NutritionFacts(int servingSize, int servings, int calories,
  21. int fat, int sodium) {
  22. this(servingSize, servings, calories, fat, sodium, 0);
  23. }
  24. public NutritionFacts(int servingSize, int servings int calories,
  25. int fat, int sodium, int carbohydrate) {
  26. this.servingSize = servingSize;
  27. this.servings = servings;
  28. this.calories = calories;
  29. this.fat = fat;
  30. this.sodium = sodium;
  31. this.carbohydrate = carbohydrate;
  32. }
  33. }

如果参数非常多,就要写大量的构造器。在使用的时候一不小心颠倒了了其中两个参数的顺序,并不能被及时的发现,但是程序运行的时候则不是正确的结果。这个时候就可以使用Builder模式了。不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器,得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类。如下:

  1. public class NutritionFacts {
  2. private final int servingSize;
  3. private final int servings;
  4. private final int calories;
  5. private final int fat;
  6. private final int sodium;
  7. private final int carbohydrate;
  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 carbohydrate = 0;
  16. private int sodium = 0;
  17. public Builder(int servingSize, int servings) {
  18. this.servingSize = servingSize;
  19. this.servings = servings;
  20. }
  21. public Builder calories(int val) {
  22. calories = val;
  23. return this;
  24. }
  25. public Builder fat(int val) {
  26. fat = val;
  27. return this;
  28. }
  29. public Builder carbohydrate(int val) {
  30. carbohydrate = val;
  31. return this;
  32. }
  33. public Builder sodium(int val) {
  34. sodium = val;
  35. return this;
  36. }
  37. public NutritionFacts build() {
  38. return new NutritionFacts(this);
  39. }
  40. private NutritionFacts(Builder builder) {
  41. servingSize = builder.servingSize;
  42. servings = builder.servings;
  43. calories = builder.calories;
  44. fat = builder.fat;
  45. sodium = builder.sodium;
  46. carbohydrate = builder.carbohydrate;
  47. }
  48. }

注意NutritionFacts是不可变的,所有的默认参数都单独放在一个地方。builder的setter方法返回builder本身,以便可以把调用链接起来,如果fat和sodium不需要,那么可以这样创建:

  1. public static void main(String[] args) {
  2. NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
  3. .calories(100)
  4. .carbohydrate(27)
  5. .build();
  6. ...
  7. }

设置了参数的builder生成了一个很好的抽象工厂,换句话说,客户端可以将这样一个builder传给方法,使该方法能够为客户端创建一个或者多个对象。

  1. public interface Builder<T> {
  2. public T build();
  3. }

带有Builder实例的方法通常利用有限制的通配符类型来约束构建器的类型参数。下面就是构建每个节点的方法,它利用一个客户端提供的Builder实例来构建树:
Tree BuilderTree(Builder<? extends Node> nodeBuilder) {...}

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注