Java/Effective Java

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

메성 2019. 12. 14. 22:38
반응형

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

점층적 생성자 패턴

  • 생성자를 매개변수 개수에 따라 계속적으로 추가하는 패턴을 말함
  • 이 클래스의 인스턴스를 만드려면 원하는 매개변수를 모두 포함하는 생성자 중 가장 짧은 것을 골라 호출하면 되는데, 클라이언트 입장에서 매개 변수로 불필요한 변수를 삽입할 때 헷갈릴 수 있다,
  • 결국, 클라이언트가 실수로 매개변수의 순서를 바꾸어 값이 들어오면 런타임 에러가 발생할 수 있다.
  • 이런 단점을 해결하기 위해 자바빈즈 패턴을 활용한다.

자바빈즈 패턴

  • 기본 생성자만을 만들고 매개변수로 필요한 값들은 set메서드를 활용해서 객체를 완성하는 특징이다.
  • 하지만 자바빈즈 패턴에서는 객체 하나를 만들려면 메서드를 여러 개 호출해야하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.
  • 점층적 생성자 패턴에서는 매개변수들이 유효한지를 생성자에서만 확인하면 일관성을 유지할 수 있었는데, 그 장치가 완전히 사라진 것이다.
  • 이런 단점을 해결하기 위해 빌더 패턴을 활용한다.

빌드 패턴

  • 점층적 생성자 패턴의 안전성과 자바 빈즈 패턴의 가독성을 가져온 패턴이다.
  • 클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻는다.
  • 그런 다음 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수들을 설정한다.
  • 마지막으로 매개변수가 없는 build 메서드를 호출해 우리에게 필요한 객체를 얻는다.
  • 소스로 살펴보자
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    ....

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

        //선택 매개변수
        private final int calories = 0;
        private final int fat = 0;

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

        //일종의 Setter 메서드들
        public Builder calories(int val) {
            calories = val;
            return this;    //.으로 체인화 가능
        }
        public Builder fat(int val) {
            fat = val;
            return this;
        }

        //마지막으로 매개변수가 없는 build 메서드 호출로 객체를 얻음
        public NutritionFacts build() {
            return new NutritoinFacts(this);
        }
    }

     private NutritionFacts(Builder builder) {
            servingSize = builder.servingSize;
            servings = builder.servings;
            calories = builder.calories;
            fat = builder.fat;
        }   
    }
}

//Main
public class Main {
    public static void main(String[] args) {
        //Builder Pattern
        NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).builder();
    }
}
  • 이런 방식을 메서드 호출이 물 흐르듯 연결된다는 뜻으로 플루언트 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.requireNonNull(topping));
                return self();
            }
    
            abstract Pizza build();
    
            //하위 클래스는 이 메서드를 재정의하여 this를 반환해야 함
            protected abstract T self();
        }
    
        Pizza(Builder<?> builder){
            toppings = builder.toppings.clone();
        }
    }
    • Pizza.Builder 클래스는 재귀적 타입 한정(아이템 30)을 이용한 제네릭 타입이다.
    • 여기에 self()를 더해 하위 클래스에서는 형변환을 하지 않고 메서드 연쇄를 지원할 수 있다.(하위 클래스에서 this를 반환)
  • //뉴욕 피자
    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 sauceInsize = false;
    
            public Builder sauceInsize(){
                sauceInsize = 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.sauceInsize;
        }
    }
    
    //Main
    public class main{
        public static void main(String[] args) {
            NyPizza nyPizza = new NyPizza.Builder(NyPizza.Size.SMALL).addTopping(Pizza.Topping.SAUSAGE).addTopping(Pizza.Topping.ONION).build();
            Calzone calzone = new Calzone.Builder().addTopping(Pizza.Topping.HAM).sauceInsize().build();
        }
    }
    
    • 각 하위 클래스의 빌더가 재정의한 build 메서드는 하위 클래스의 인스턴스를 반환하도록 선언한다.
    • NyPizza.Builder는 NyPizza를 반환, Calzone.Builder는 Calzone을 반환한다는 뜻이다.
    • 즉, 하위 클래스가 하위 타입의 클래스를 반환하므로 형변환이 따로 필요로 하지 않는다.
    • 메서드를 여러번 호출하도록 하고, 각각에서 호출할 때 넘겨진 매개변수들을 하나의 필드로 모을 수도 있는데, 이를 활용한 것이 addTopping이다.
      • Builder.addTopping의 반환값은 self() 즉, 하위 클래스 자신이므로 하위클래스는 상속받은 Builder의 addTopping에 접근이 가능하여 각 호출때 넘겨진 매개변수들을 하나의 필드로 모으는 것이다.
  • 빌더 패턴은 상당히 유연하다. 빌더 하나로 여러 객체를 순회하면서 만들 수 있고, 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수도 있다.

http://www.yes24.com/Product/Goods/65551284?scode=032&OzSrank=1

 

이펙티브 자바 Effective Java 3/E

자바 플랫폼 모범 사례 완벽 가이드 - Java 7, 8, 9 대응자바 6 출시 직후 출간된 『이펙티브 자바 2판』 이후로 자바는 커다란 변화를 겪었다. 그래서 졸트상에 빛나는 이 책도 자바 언어와 라이브러리의 최신 기능을 십분 활용하도록 내용 전반을 철저히 다시 썼다. 모던 자바가 여러 패러다임을 지원하기 시작하면서 자바 개발자들에게는...

www.yes24.com

 

반응형