반응형

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

점층적 생성자 패턴 - 확장하기 어렵다!

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

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

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

자바빈즈 패턴 - 일관성이 깨지고 불변으로 만들 수 없다.

public class NutritionFacts {
    private int servingSize = -1;
    private int servings = -1;
    private final int calories = 0;
    private final int fat = 0;
    private final int sodium = 0;
    private final int carbohydrate = 0;

    public NutritionFacts(){}

    public void setServingSize(int val){servingSize = val;}
    public void setServings(int val){servings = val;}
    public void setCalories(int val){calories = val;}
    public void setFat(int val){fat = val;}
    public void setSodium(int val){sodium = val;}
    public void setCarbohydrate(int val){ carbohydrate = val;}

}

자바빈즈 패턴에서는 객체 하나를 만들려면 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 됨

자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없음(아이템 17_변경 가능성을 최소화하라)

final 등 불변을 보장, 스레드 안정하도록 구현이 필요함

위 두가지를 아우리는게 세번째 대안인 빌터 패턴이다

빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취함

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

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

}

//클라이언트 후출시
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
        .calories(100).sodium(35).carbohydrate(27).build();

메서드 호출이 흐르듯이 연결된다는 뜻으로 플루언트 API 혹은 메서드 연쇄라 한다.

계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴

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.requiredNonNull(topping));
            return self();
        }

        abstract Pizza build();

        protected abstract T self();
    }

    Pizza(Builder<?> builder){
        toppings = builder.toppings.clone();
    }
}

재귀적 타입 하전을 이용하는 제네릭 타입
추상 메서드인 self를 더해 하위 클래스에서 형변환하지 않고도 메서드 연쇄를 지원

뉴욕피자

public class NyPizza extends Pizza {
    public enum Size {SMALL, MEDIUM, LARGE}
    private final Size size; 

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

        public Builder(Size size){
            this.size = Objects.requiredNonNull(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 NyPizza build() {
return new NyPizza(this);
}
다음 구체 하위 클래스는 하위 클래스 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌, 그 하위 타입을 반환하는 기능을 공병환 타이핑이라 한다.
클라이언트가 형변환에 신경 쓰지 않고도 빌더를 사용할 수 있음

NyPizza pizza = new NyPizza.Builder(SMALL)
        .addTopping(SAUSAGE).addTopping(ONION).build();

생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다.

실무에서는 롬복에서 제공하는 Builder 어노테이션을 잘 활용하면 될 듯하다.

728x90

+ Recent posts