[Effective Java 3rd] item2. 생성자와 매기변수가 많다면 빌더를 고려하라.

정적팩터리와 생성자 모두 선택적 매개변수가 많을 때 적절히 대응하기가 어렵다.
매개변수가 많을 때 사용할 수 있는 방법들을 살펴보자.

  1. 점층적 생성자 패턴
public class NutrionFacts {

    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 NutrionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutrionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories,0);
    }

    public NutrionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories,fat, 0);
    }

    public NutrionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
        this(servingSize, servings, calories,fat, sodium,0);
    }

    public NutrionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }

    public static void main(String[] args) {
        NutrionFacts cocaCola = new NutrionFacts(240,8, 100, 0 ,35, 27);

    }
}

매개변수가 많아지고, 필수와 선택으로 나누어서 생성자를 만드니 가독성이 떨어진다. 각 값의 의미를 이해가 어렵다
매개변수 순서를 변경해서 작성해도 컴파일러는 알아차리지 못해 엉뚱한 동작을 하게 만든다.

  1. 자바 빈즈 패턴
public class NutrionFactsJavaBeans {
    private int servingSize = -1;
    private int servings = -1;
    private int calories =0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate =0;

    public NutrionFactsJavaBeans(){

    }

    public void setServingSize(int servingSize) {
        this.servingSize = servingSize;
    }

    public void setServings(int servings) {
        this.servings = servings;
    }

    public void setCalories(int calories) {
        this.calories = calories;
    }

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

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

    public void setCarbohydrate(int carbohydrate) {
        this.carbohydrate = carbohydrate;
    }

    public static void main(String[] args) {
        NutrionFactsJavaBeans cocaCola = new NutrionFactsJavaBeans();
        cocaCola.setServings(240);
        cocaCola.setServings(8);
    }
}

자바 빈즈 패턴을 이용하면 객체 하나를 만드는데 여러개의 메서드를 호출해야 하고, 객체가 완전히 생성되기 전까지 일관성이 무너진다.
자바빈즈 패턴에서는 setter가 열려있어 클래스로 불변을 만들 수 없다.

  1. 빌더패턴 : 점층적 생성자 패턴(안정성) + 자바빈즈 패턴의 가독성 겸비
1) 첫번째 빌더 패턴
public class NutritionFactsBuilder {

    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 {
        //필수 매개변수
        private final int servingSize;
        private final int servings;

        //선택 매개변수 - 기본값으로 초기화
        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 sodium(int val){
            sodium=val; return this;
        }

        public Builder carbohydrate(int val){
            carbohydrate=val; return this;
        }

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

    public NutritionFactsBuilder(Builder builder) {
        this.servingSize = builder.servingSize;
        this.servings = builder.servings;
        this.calories = builder.calories;
        this.fat = builder.fat;
        this.sodium = builder.sodium;
        this.carbohydrate = builder.carbohydrate;
    }

    public static void main(String[] args) {
        NutritionFactsBuilder cocaCola = new Builder(240,8)
                .calories(100).sodium(35).carbohydrate(27).build();
    }
}

빌더를 이용하여 필수와 선택요소를 나눌 수 있고 마지막에 build()를 실행하여 instance 생성이 가능하다.
build메서드가 호출하는 생성자에서 불변식을 검사하자. 매개변수가 잘못되었는지를 알려주는 메시지를 담아 IllegalArgumentException을 던지면 된다.

2) 계층적으로 설계된 클래스와 함께 쓰이는 빌더패턴
피자의 다양한 종류를 표현하는 계층구조의 루트에 놓인 추상 클래스

public abstract 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 addTopping(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(); //아이템 50??
    }
}

 

Pizza 추상화 클래서를 상속받은 NyPizza

public class NyPizza 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 = Objects.requireNonNull(size);
        }

        @Override
        public NyPizza build(){
            return new NyPizza(this);

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


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

    public static void main(String[] args) {
        NyPizza pizza = new NyPizza.Builder(Size.SMALL)
                .addTopping(Topping.SAUSAGE). addTopping(Topping.ONION).build();
    }
}

 

NyPizza.Builder는 NyPizza를 반환한다.
생성자로 누릴 수 없는 사소한 이점으로, 빌더를 이용하면 가변인수 매개변수를 여러개 사용할 수있다.
각각의 적절한 메서드로 나눠 선언하면 된다.
빌더패턴은 상당히 유연하다. 빌더 하나로 여러 객체를 순회하면서 만들 수 있다.
객체를 만들려면, 그에 앞서 빌더부터 만들어야한다.
매개변수가 4개 이상이 되어야 값어치가 있다.
API는 시간이 지날수록 매개변수가 많아지는 경향이 있음을 명심하자.
애초에 빌더로 시작하는 편이 나을 수도 있다.

 

 

참고 :
빌더

https://devfunny.tistory.com/337

롬복을 이용한 Builder

 

빌더 패턴의 권장 이유

생성자의 단점 생성자에는 제약이 하나 있는데, 선택적 매개변수가 많을 경우에 대응이 어렵다. 예를들어, 받아오는 매개변수에 따라 계속해서 생성되는 생성자의 코드를 보았을때 매개변수의

devfunny.tistory.com

https://zorba91.tistory.com/298

 

[Spring] Lombok을 이용해 Builder 패턴을 만들어보자.

Builder 패턴이란? Effective Java 규칙 2 - 조슈아 블로크 생성자에 인자가 많을 때는 빌더 패턴을 고려하라 빌더 패턴(Builder pattern) 이란 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성

zorba91.tistory.com

 

댓글

Designed by JB FACTORY