티스토리 뷰

반응형

생성자에 매개변수가 많다면 빌더를 고려하라

생성자가 많고, 그중 null값으 많이 들어가는 것이 있다면 빌더 패턴을 사용하는 것이 좋다.

책에서는 먼저 점층적 생성자 패턴을 제시한다.

public class NutritionFacts {
    private final int servingSize;  //(ml, 1회 제공량)
    private final int servings;     //(회, 총 n회 제공량)
    private final int calories;     //(1회 제공량당)
    private final int fat;          //(g/1회 제공량)
    private final int sodium;       //(mg/1회 제공량)
    private final int carbohydrate;

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

    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, O);
    }

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

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

    public NutritionFacts(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;
    }
}

하지만 위의 해결책도 근본적인 해결을 내놓지는 못한다.
왜냐하면 결국 매개변수의 갯수가 많ㅇ지면 클라이언트 코드를 작성하거나 읽기 어려워진다는 점이 있기 때문이다.
또한 순서를 클라이언트가 제대로 숙지하지 않는다면 뒤섞인 값이 들어가기 때문에 더 엉망진창의 코드가 될 수 있다는 점도 있다.

 

setter의 단점.
1 객체 하나 생성시 메서드 호출 빈도가 많다.
2 객체가 완성되기 전까지 일관성이 무너진 상태이다.
3 불변객체를 만드는게 불가능하다.(스레드의 안정성)
4 일관성이 깨진 객체가 만들어지면, 버그를 심은 코드와 그 버그 때문에 런타임 문제를 겪는 코드가 물리적으로 멀리 떨어져 있으므로 디버깅 또한 어렵다.

 

여기서 해결책이 빌더 패턴이다.

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 {
        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 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;
    }
}

Builder는 자기자신을 반환하기 때문에 연쇄적으로 호출해 값을 넣는 것이 가능하다.

NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
        .calories(100).sodium(35).carbohydrate(27).build();

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

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.none0f (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(); // 010|8 50 &2
    }
}

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 class Calzone extends Pizza {
    private final boolean sauceInside;

    public static class Builder extends Pizza. Builder<Builder> {
        private boolean sauceInside;

        public Builder sauceInside() {
            sauceInside = true;
        }

        @Override public Calzone build() {
            return new Calzone(this);
        }
        @Override protected Builder self() { return this; }
    }

    private Calzone(Builder builder) {
        super (builder);
        sauceInside = builder.sauceInside;
    }
}

다음과 같이 NyPizza.Builder와 Calzone.Builder를 보면 반환타입을 달리 할 수 있다.

빌더패턴의 단점으로는 생성 비용에 있다. 성능에 민감함 상황이라면 사용ㅇ하지 않는 것이 좋다.
그리고 내가 가장 중요하게 본 것은 매개변수가 꼭 많을때 사용해야할 것 같았다. 왜냐하면 빌더로만 객체를 만들면 오히려 더 복잡해질것 같아서이다.

728x90
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함
250x250