아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라 - KwangtaekJung/book-effective-java GitHub Wiki

아이템2. 생성자에 매개변수가 많다면 빌더를 고려하라.

정적 팩터리와 생성자의 공통적인 제약 사항

  • 선택적 매개변수가 많을 경우 적절히 대응하기가 어렵다.

대안

  • 점층적 생성자 패턴(telescoping constructor pattern)
  • 자바빈즈 패턴(JavaBeans Pattern)
  • 빌더 패턴(Builder Pattenr)
    • 점층적 생성자 패턴의 안정성과 자바빈즈 패턴의 가독성을 겸비함.
    • 계층적으로 설계된 클래스와 함께 쓰기에 좋음.

코드

  • 코드2.3 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패펀의 장정만 취했다.
public class NutritionFactsWithCollections {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;
    private final Set<String> sauces;

    public static NutritionFactsWithCollections.Builder builder(int servingSize, int servings) {
        return new NutritionFactsWithCollections.Builder(servings, servings);
    }

    public static class Builder {
        //필수 매개변수
        private final int servingSize;
        private final int servings;

        //선택 매개변수 - 기본값으로 초기화한다.
        private int calories = 0;
        private int fat =0;
        private int sodium = 0;
        private int carbohydrate = 0;
        private final Set<String> sauces = new HashSet<>();

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            this.calories = val;
            return this;
        }
        public Builder fat(int val) {
            this.fat = val;
            return this;
        }
        public Builder sodium(int val) {
            this.sodium = val;
            return this;
        }
        public Builder carbohydrate(int val) {
            this.carbohydrate = val;
            return this;
        }
        public Builder addSauces(String sauce) {
            this.sauces.add(sauce);
            return this;
        }

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

    private NutritionFactsWithCollections(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
        sauces = builder.sauces;
    }

    @Override
    public String toString() {
        return "NutritionFactsWithCollections{" +
                "servingSize=" + servingSize +
                ", servings=" + servings +
                ", calories=" + calories +
                ", fat=" + fat +
                ", sodium=" + sodium +
                ", carbohydrate=" + carbohydrate +
                ", sauces=" + sauces +
                '}';
    }
}
  • Lombok을 이용한 방법
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)  //@Builder를 사용하면 전체 인자를 갖는 생성자를 자동으로 만들어준다. 이때 이렇게 private로 선언하도록 만들어주면 생성자로는 인스턴스를 생성하지 못하도록 막을 수 있다.
@ToString
public class NutritionFactsLombok {
    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 class Item02Main {
    public static void main(String[] args) {

        //직접 Builder 구현 - Collection이 포함되어 있는 경우
        NutritionFactsWithCollections nutritionFactsWithCollections = NutritionFactsWithCollections.builder(10, 20)
                .calories(100)
                .fat(200)
                .sodium(300)
                .carbohydrate(400)
                .addSauces("ham")
                .addSauces("red pepper")
                .build();
        System.out.println("nutritionFactsWithCollections = " + nutritionFactsWithCollections);

        //Lombok 사용
        NutritionFactsLombok nutritionFactsLombok = NutritionFactsLombok.builder()
                .servingSize(10)
                .servings(5)
                .fat(100)
                .sodium(200)
                .carbohydrate(300)
                .build();
        System.out.println("nutritionFactsLombok = " + nutritionFactsLombok);
    }
}

빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.

public class Pizza {
    public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
    final Set<Topping> toppings;

    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
        public T addToping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }

        abstract Pizza build();

        //하위 클래스는 이 메서드를 재정의(overriding)하여
        //"this"를 반환하도록 해야 한다.
        protected abstract T self();
    }

    Pizza(Builder<?> builder) {
        toppings = builder.toppings.clone();
    }
}
public class MyPizza extends Pizza {
    public enum Size { SMALL, MEDIUM, LARGE };
    private final Size size;

    public static class Builder extends Pizza.Builder<Builder> {
        private final Size size;

        public Builder(Size size) {
            this.size = size;
        }

        @Override
        MyPizza build() {
            return new MyPizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    private MyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }

    @Override
    public String toString() {
        return "MyPizza{" +
                "size=" + size +
                ", toppings=" + toppings +
                '}';
    }
}
        // '계츧적 빌더'를 사용하는 클라이언트 코드
        MyPizza myPizza = new MyPizza.Builder(MyPizza.Size.SMALL)
                .addToping(Pizza.Topping.SAUSAGE)
                .addToping(Pizza.Topping.ONION)
                .build();
        System.out.println("myPizza = " + myPizza);
⚠️ **GitHub.com Fallback** ⚠️