Skip to content

Latest commit

 

History

History
317 lines (246 loc) · 8.64 KB

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

File metadata and controls

317 lines (246 loc) · 8.64 KB

아이템2 | 생성자에 매개변수가 많다면 빌더를 고려하라

정적 팩토리 메서드와 생성자의 제약사항

  • 선택적 매개변수가 많을 때 적절히 대응하기가 어려움
public class User {
    private final int age;         //필수
    private final int height;      //필수
    private final int weight;      //필수
    private final int yearJoined;  //선택
    private final int level;       //선택
    private final int score;       //선택
}

점층적 생성자 패턴

필수 매개변수를 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자, 필수 매개변수와 선택 매개변수 2개를 받는 생성자… 형태로 모든 매개변수를 받을 때 까지 늘려가는 방식

public class User { 
    private final int age;         //필수
    private final int height;      //필수
    private final int weight;      //필수
    private final int yearJoined;  //선택
    private final int level;       //선택
    private final int score;       //선택

    public User(int age, int height, int weight, int yearJoined) {
        this(age, height, weight, yearJoined, 0);
    }

    public User(int age, int height, int weight, int yearJoined, int level) {
        this(age, height, weight, yearJoined, level, 0);
    }

    public User(int age, int height, int weight, int yearJoined, int level, int score) {
        this.age = age;
        this.height = height;
        this.weight = weight;
        this.yearJoined = yearJoined;
        this.level = level;
        this.score = score;
    }
}    

점층적 생성자 패턴은 매개변수가 많아질 수록 코드를 작성하거나 읽기 어려움

User user = new User(26,180, 75, 2024, 0, 40);
  • 각 값의 의미가 헷갈림
  • 매개변수의 개수를 주의깊게 살펴야함
  • 타입이 같은 매개변수의 순서가 바뀌게 되면 컴파일 단계에서 찾기 어려움

자바빈즈 패턴

매개변수가 없는 생성자로 객체를 만든 후, 세터(setter) 메서드를 호출해 값을 설정하는 방식

public class User {
    private int age;
    private int height;
    private int weight;
    private int yearJoined;
    private int level;
    private int score;
			
    public User() {
    }
			
    public void setAge(int age) {
        this.age = age;
    }
			
    public void setHeight(int height) {
        this.height = height;
    }
			
    public void setWeight(int weight) {
        this.weight = weight;
    }
			
    public void setYearJoined(int yearJoined) {
        this.yearJoined = yearJoined;
    }
			
    public void setLevel(int level) {
        this.level = level;
    }
			
    public void setScore(int score) {
        this.score = score;
    }
}

자바빈즈 패턴은 점층적 패턴의 단점을 해결하고 좀 더 읽기 쉬운 코드를 만듬

User user = new User();
user.setAge(25);
user.setHeight(180);
user.setWeight(75);
user.setYearJoined(2020);
user.setLevel(5);
user.setScore(100);

자바빈즈 패턴의 단점

  • 객체를 만들기 위해서 여러개의 메서드를 호출해야함
  • 객체가 완전히 생성되기 전에는 일관성이 무너진 상태가 됨
  • 일관성이 없기 때문에 객체를 불변으로 생성할 수 없음

cf) 일관성이 무너진다의 의미

  • 객체의 상태가 예측할 수 없게 변함
  • 객체가 불완전한 상태에 놓일 수 있음

빌더 패턴

필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻어 원하는 선택 매개변수를 설정한 뒤 build 메서드를 호출해 객체를 얻음

public class User {
    private final int age;
    private final int height;
    private final int weight;
    private final int yearJoined;
    private final int level;
    private final int score;
    
    private User(Builder builder) {
        this.age = builder.age;
        this.height = builder.height;
        this.weight = builder.weight;
        this.yearJoined = builder.yearJoined;
        this.level = builder.level;
        this.score = builder.score;
    }
   
    public static class Builder {
        private int age;
        private int height;
        private int weight;
        
        //선택 매개변수를 기본값으로 초기화
        private int yearJoined = 0;
        private int level = 0;
        private int score = 0;

        public Builder() {
        }

        public Builder age(int age) {
        //각 변수를 set 하기전 검증 가능
            this.age = age;
            return this;
        }

        public Builder height(int height) {
            this.height = height;
            return this;
        }

        public Builder weight(int weight) {
            this.weight = weight;
            return this;
        }

        public Builder yearJoined(int yearJoined) {
            this.yearJoined = yearJoined;
            return this;
        }

        public Builder level(int level) {
            this.level = level;
            return this;
        }

        public Builder score(int score) {
            this.score = score;
            return this;
        }
        
        public User build() {
            return new User(this);
        }
    } 
}
  • 점층적 생성자 패턴의 안정성 + 자바빈즈 패턴의 가독성을 모두 가짐
  • 빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출 할 수 있음
    • 이런 방식을 플루언트API 혹은 메서드 연쇄라 함
User user = new User.Builder()
        .age(30)
        .height(175)
        .weight(70)
        .yearJoined(2022)
        .level(5)
        .score(100)
        .build();

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

public abstract class Pizza{
   public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
   final Set<Topping> toppings;

   // 추상 클래스는 추상 Builder를 가진다. 서브 클래스에서 이를 구체 Builder로 구현한다.
   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();
   }
}

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

추상 클래스와 추상 빌더를 생성하여 구체 클래스는 구체 빌더를 가지게 함

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

Calzone calzone = new Calzone.Builder()
                .addTopping(HAM)
                .sauceInside()
                .build();
  • 빌더를 이용하면 가변인수 매개변수를 여러 개 사용할 수 있음 ex) addTopping
  • 빌더 하나로 여러 객체를 순회하면서 만들 수 있고, 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수도 있음

빌더 패턴의 단점

  • 객체를 만들기 위해서는 빌더부터 만들어야함
  • 추가되는 코드가 많아 매개변수 4개 이상부터 값어치를 함

결론

  • 생성자나 정적 팩터리 메서드가 처리해야할 메서드가 많거나, 매개변수 중 다수가 필수가 아니라면 빌더 패턴을 선택하는 것이 나음
  • 빌더는 점층적 생성자보다 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 안전함