빌더 패턴

코딩으로 배우는 GoF의 디자인 패턴

건축업자

동일한 프로세스를 통해 다른 구성의 인스턴스를 생성하는 방법


Director추가 기능이며 실제 빌더 패턴은 모두 빌더 인터페이스 및 해당 구현에 관한 것입니다.

TourPlanBuilder

  • 빌더의 인터페이스. 각 메서드는 필드명을 메서드명으로 지정하고 같은 타입을 파라미터로 받습니다.
  • 반환 유형은 TourBuilderInterface이렇게 하면 연결이 가능합니다.
  • 마지막 build()실제 객체를 종료로 반환해야 하므로 반환 유형 TourPlan장차 ~ 가 되는
public interface TourPlanBuilder {  
   TourPlanBuilder nightsAndDays(int nights, int days);  

   TourPlanBuilder title(String title);  

   TourPlanBuilder startDate(LocalDate startDate);  

   TourPlanBuilder whereToStay(String whereToStay);  

   TourPlanBuilder addPlan(int day, String plan);  

   TourPlan build();  
}

DefaultTourBuilder

  • TourPlanBuilder의 구현
    • 빌더를 구성하는 방법에는 두 가지가 있습니다.
    • 필드에 생성할 객체를 입력하고 필드 값을 set로 입력(현재 방식)
    • 빌더 필드에 생성할 오브젝트의 모든 필드를 입력한 후 build()도시 생성자를 통해 생성
  • 재정의된 메서드는 해당 필드에 주입하고 개체 자체를 반환합니다. (이것)
  • build()필요한 객체를 반환합니다.
public class DefaultTourBuilder implements TourPlanBuilder{  

   private TourPlan tourPlan;  

   public DefaultTourBuilder() {  
      this.tourPlan = new TourPlan();  
   }  

   public static TourPlanBuilder builder() {  
      return new DefaultTourBuilder();  
   }  

   @Override  
   public TourPlanBuilder nightsAndDays(int nights, int days) {  
      tourPlan.setNights(nights);  
      tourPlan.setDays(days);  
      return this;  
   }  

   @Override  
   public TourPlanBuilder title(String title) {  
      tourPlan.setTitle(title);  
      return this;  
   }  

   @Override  
   public TourPlanBuilder startDate(LocalDate startDate) {  
      tourPlan.setStartDate(startDate);  
      return this;  
   }  

   @Override  
   public TourPlanBuilder whereToStay(String whereToStay) {  
      tourPlan.setWhereToStay(whereToStay);  
      return this;  
   }  

   @Override  
   public TourPlanBuilder addPlan(int day, String plan) {  
      if(tourPlan.getPlans() == null)  
         tourPlan.setPlans(new ArrayList<>());  
      tourPlan.getPlans().add(new DetailPlan(day, plan));  
      return this;  
   }  

   @Override  
   public TourPlan build() {  
      return tourPlan;  
   }  

}

투어디렉터

  • 개체를 한 번 더 래핑하는 방법
  • 일반적으로 자주 사용하는 패턴이 있으면 Director의 방식으로 지정하는 것이 좋은 방법입니다.
public class TourDirector {  

   public TourPlan tour1() {  
      return DefaultTourBuilder.builder()  
         .title("칸쿤 여행")  
         .nightsAndDays(2,3)  
         .startDate(LocalDate.of(2020,12,9))  
         .whereToStay("리조트")  
         .addPlan(0, "체크인하고 짐 풀기")  
         .addPlan(0, "저녁 식사")  
         .build();  
   }  

   public TourPlan tour2() {  
      return DefaultTourBuilder.builder()  
         .title("롱비치")  
         .startDate(LocalDate.of(2021, 7, 25))  
         .build();  
   }  
}

  • 사용 예
public class App {  

   public static void main(String() args) {  
      // 빌더 사용하기
      TourPlan tour1 = DefaultTourBuilder.builder()  
         .title("칸쿤 여행")  
         .nightsAndDays(2,3)  
         .startDate(LocalDate.of(2020,12,9))  
         .whereToStay("리조트")  
         .addPlan(0, "체크인하고 짐 풀기")  
         .addPlan(0, "저녁 식사")  
         .build();  

      TourPlan tour2 = DefaultTourBuilder.builder()  
         .title("롱비치")  
         .startDate(LocalDate.of(2021, 7, 25))  
         .build();  

      // Director 사용하기  
      TourDirector director = new TourDirector();  
      TourPlan directedTour1 = director.tour1();  
      TourPlan directedTour2 = director.tour2();  
   }  
}

장점과 단점

장점

  1. 복잡한 개체를 순차적으로 만들 수 있습니다. (디자인이 좋으면)
  2. 개체를 만드는 과정을 숨길 수 있습니다.
  3. 동일한 프로세스를 통해 다른 개체를 만들 수 있습니다.
  4. 불완전한 개체 사용을 방지할 수 있습니다.

불리

  1. 원하는 개체를 만들려면 먼저 빌더 개체를 만들어야 합니다.
  2. 구조가 복잡해집니다.

개인적인 의견

+ 가독성

개인적으로는 생성자가 아닌 빌더 패턴을 사용하여 객체를 생성할 때 가독성이 좋아진다고 생각합니다. 필드명을 메서드로 직접 지정하기 때문에 실수가 줄어듭니다. 생성자를 사용하여 개체를 만드는 것보다 훨씬 직관적입니다.

+유용성

생성자를 통한 객체 생성에서 입력 매개변수가 다른 생성자를 사용하려면 새로운 생성자를 생성해야 합니다. 경우의 수가 증가할수록 생성해야 하는 생성자 수도 증가합니다. 빌더 패턴은 모든 매개변수로 생성자를 생성하며, 빌드 시 입력되지 않은 필드의 값은 null이거나 기본값일 수 있습니다.
생성자를 사용하여 객체를 생성하는 것도 가능하지만 생성자 매개변수의 순서는 항상 알고 있어야 합니다. 빌더 오브젝트는 순서가 없기 때문에 필요한 필드 값만 입력하고 빌드하면 자동으로 처리되기 때문에 다양한 조건의 오브젝트를 보다 편리하게 생성할 수 있습니다.

-복잡성

빌더는 체이닝 방식으로 설정을 이어가며 단순한 객체 생성 뿐만 아니라 다양한 활용이 가능합니다. 함수형 인터페이스를 받아서 람다 식을 사용할 수도 있고, 빌더 안에 다른 빌더를 넣을 수도 있고, 여러 빌더를 상속받아 거대한 빌더를 형성할 수도 있습니다. 이것을 이해하면 직관적으로 다가오지만 처음 접할 때는 상당히 복잡하게 느껴진다. 그래서 크기가 크면 복잡해진다는 단점이 있지만, 이해하기 복잡해서 코드가 깔끔하게 작성된다. 그래서 빌더를 사용하기 위해서는 빌더에 대한 이해가 어느 정도 필요하다고 생각합니다.