ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [이펙티브 자바 3판 2장] 아이템2. 생성자에 매개변수가 많다면 빌더를 고려하자
    책정리/이펙티브 자바 3판 2020. 7. 25. 18:05

    점층적 생성자 패턴도 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.

    setter 를 통해서 객체값 세팅(자바빈즈 패턴) 에서는 객체 하나를 만들기 위해 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너지게 된다.

    자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없다.

    이러한 객체 불변과 점층적 생성자 패턴의 대안은 빌더 패턴이다.

     

    빌더는 생성할 클래스 안에 정적 멤버 클래스로 만들어두는게 보통이다.

    // 코드 2-3 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다. (17~18쪽)
    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;
        }
    
        public static void main(String[] args) {
            NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                    .calories(100).sodium(35).carbohydrate(27).build();
        }
    }

    빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출 할 수 있다. 이런 방식을 플루언트 API 혹은 메서드 연쇄(method chaning) 라고 한다.

     

     

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

    추상 클래스는 추상빌더를 구체 클래스는 구체 빌더를 갖게 한다,

    // 코드 2-4 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴 (19쪽)
    
    // 참고: 여기서 사용한 '시뮬레이트한 셀프 타입(simulated self-type)' 관용구는
    // 빌더뿐 아니라 임의의 유동적인 계층구조를 허용한다.
    
    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 참조
        }
    }

    추상메서드인 self 를 더해 하위 클래스에서는 형변환하지 않고드 메서드 연쇄를 지원할 수 있다.

     

    // 코드 2-5 뉴욕 피자 - 계층적 빌더를 활용한 하위 클래스 (20쪽)
    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;
        }
    
        @Override public String toString() {
            return toppings + "로 토핑한 뉴욕 피자";
        }
    }
    // 코드 2-6 칼초네 피자 - 계층적 빌더를 활용한 하위 클래스 (20~21쪽)
    public class Calzone extends Pizza {
        private final boolean sauceInside;
    
        public static class Builder extends Pizza.Builder<Builder> {
            private boolean sauceInside = false; // 기본값
    
            public Builder sauceInside() {
                sauceInside = true;
                return this;
            }
    
            @Override public Calzone build() {
                return new Calzone(this);
            }
    
            @Override protected Builder self() { return this; }
        }
    
        private Calzone(Builder builder) {
            super(builder);
            sauceInside = builder.sauceInside;
        }
    
        @Override public String toString() {
            return String.format("%s로 토핑한 칼초네 피자 (소스는 %s에)",
                    toppings, sauceInside ? "안" : "바깥");
        }
    }

    각각의 Calzone 과 NyPizza Builder 는 NyPizza 와 Calzone 를 반환한다. 하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌 그 하위 타입을 반환하는 기능을 공변 반환 타이핑(covariant return typing) 이라 한다.

    이 기능을 이용하면 클라이언트가 형변환에 신경 쓰지 않고도 빌더를 사용할 수 있다.

    반응형

    댓글

Designed by Tistory.