✍️ 빌더 패턴, 적용하기
빌더 패턴을 적용하려면 빌더 인터페이스를 만들어야 한다.
빌더 인터페이스는 일관된 프로세스로 인스턴스를 구성하고 생성할 수 있도록 추상화된 메서드를 제공해야 한다.
public interface TourPlanBuilder {
TourPlanBuilder title(String title);
TourPlanBuilder nightsAndDays(int nights, int days);
TourPlanBuilder startDate(LocalDate startDate);
TourPlanBuilder whereToStay(String whereToStay);
TourPlanBuilder addPlan(int day, String plan);
TourPlan getPlan();
}
추상 메서드들을 살펴보면 공통적인 특징으로 TourPlanBuilder를 반환한다. 멤버 변수 초기화 메서드들이 TourPlanBuilder를 반환하는 몇 가지 이유 중 하나가 메서드 체이닝을 사용하기 위해서다.
가령 TourPlanBuilder의 구현 클래스를 사용하는 클라이언트는 title 메서드를 호출하고 나면 TourPlanBuilder 타입의 인스턴스를 반환받아 TourPlanBuilder의 또 다른 메서드를 호출할 수 있다. 클라이언트는 일련의 체이닝 과정을 거치며 인스턴스를 구성하고, 구성 작업이 끝나면 getPlan 메서드를 호출해 실제 인스턴스를 반환받는다.
구현 클래스, DefaultTourPlanBuilder
public class DefaultTourBuilder implements TourPlanBuilder {
private String title;
private int nights;
private int days;
private LocalDate startDate;
private String whereToStay;
private List<DetailPlan> plans;
@Override
public TourPlanBuilder title(String title) {
this.title = title;
return this;
}
@Override
public TourPlanBuilder nightsAndDays(int nights, int days) {
this.nights = nights;
this.days = days;
return this;
}
@Override
public TourPlanBuilder startDate(LocalDate startDate) {
this.startDate = startDate;
return this;
}
@Override
public TourPlanBuilder whereToStay(String whereToStay) {
this.whereToStay = whereToStay;
return this;
}
@Override
public TourPlanBuilder addPlan(int day, String plan) {
if (this.plans == null) {
plans = new ArrayList<>();
}
this.plans.add(new DetailPlan(day, plan));
return this;
}
@Override
public TourPlan getPlan() {
return new TourPlan(title, nights, days, startDate, whereToStay, plans);
}
}
본 코드에선 생략됐지만 getPlan 메서드에선 인스턴스가 올바른 값으로 초기화되었는지, 불안정한 상태는 아닌지에 검증하는 단계가 포함되어야 한다. 가령 초기화되지 않은 필드가 있는지 또는 장거리 여행에서 nights 와 days가 초기화되었는지와 같은 검증을 거쳐 인스턴스를 생성해야 한다.
🍊 빌더 패턴, 사용하기
클라이언트는 DefaultTourBuilder의 메서드를 체이닝 형태로 호출하며 인스턴스를 구성하고 최종적으로 Builder가 생성한 인스턴스를 반환받는다.
public class App {
public static void main(String[] args) {
TourPlanBuilder builder1 = new DefaultTourBuilder();
TourPlan jeJuPlan = builder1.title("제주도 여행")
.nightsAndDays(2, 3)
.startDate(LocalDate.of(2020, 12, 9))
.whereToStay("리조트").addPlan(0, "체크인")
.addPlan(0, "한라산 등반")
.addPlan(1, "한라봉 먹기")
.addPlan(2, "흑돼지 먹기")
.addPlan(3, "귀가")
.getPlan();
TourPlanBuilder builder2 = new DefaultTourBuilder();
TourPlan busanPlan = builder2.title("[당일치기] 부산 여행")
.startDate(LocalDate.of(2022, 5, 15))
.addPlan(0, "바닷가에서 놀기")
.getPlan();
}
}
만약 빌더를 사용하지 않고 생성자를 사용했다면
생성자의 매개변수 값들을 장황하게 작성했을 것이다.
하물며 초기화하지 않는 값들엔 null 값을 넘겨주면서 난잡한 코드가 됐을 것이다.
🥝 빌더 패턴, 디렉터 추가
빌더를 통한 인스턴스 생성이 반복적으로 이뤄진다면 디렉터 사용을 고려해 볼 필요가 있다.
아래 코드를 예로 들어, jejuTripPlan을 생성하는 코드가 반복적으로 등장한다면 디렉터의 메서드에 jejuTripPlan 생성 코드를 넣어두고 클라이언트는 이를 호출함으로써 코드를 재사용할 수 있다.
public class App {
public static void main(String[] args) {
TourPlanBuilder builder1 = new DefaultTourBuilder();
TourPlan jejuTripPlan = builder1.title("제주도 여행")
.nightsAndDays(2, 3)
.startDate(LocalDate.of(2020, 12, 9))
.whereToStay("리조트").addPlan(0, "체크인")
.addPlan(0, "한라산 등반")
.addPlan(1, "한라봉 먹기")
.addPlan(2, "흑돼지 먹기")
.addPlan(3, "귀가")
.getPlan();
TourPlanBuilder builder2 = new DefaultTourBuilder();
TourPlan busanTripPlan = builder2.title("[당일치기] 부산 여행")
.startDate(LocalDate.of(2022, 5, 15))
.addPlan(0, "바닷가에서 놀기")
.getPlan();
}
}
디렉터, TourDirector
public class TourDirector {
private TourPlanBuilder builder;
public TourDirector(TourPlanBuilder builder) {
this.builder = builder;
}
public TourPlan jejuTripPlan() {
return builder.title("제주도 여행")
.nightsAndDays(2, 3)
.startDate(LocalDate.of(2020, 12, 9))
.whereToStay("리조트").addPlan(0, "체크인")
.addPlan(0, "한라산 등반")
.addPlan(1, "한라봉 먹기")
.addPlan(2, "흑돼지 먹기")
.addPlan(3, "귀가")
.getPlan();
}
public TourPlan busanTripPlan() {
return builder.title("[당일치기] 부산 여행")
.startDate(LocalDate.of(2022, 5, 15))
.addPlan(0, "바닷가에서 놀기")
.getPlan();
}
}
디렉터는 직접 빌더의 메서드를 호출하며 일관된 프로세스로 인스턴스를 만들고
클라이언트 쪽에선 디렉터가 제공하는 메서드를 호출하므로써 코드를 재사용할 수 있다.
public class App {
public static void main(String[] args) {
TourDirector director = new TourDirector(new DefaultTourBuilder());
TourPlan jejuTripPlan = director.jejuTripPlan();
TourPlan busanTripPlan = director.busanTripPlan();
}
}
인프런의 백기선님의 강의 코딩으로 학습하는 GoF의 디자인 패턴을 참고해서 작성했습니다.
'Java > Design Pattern with Java' 카테고리의 다른 글
[객체 생성 패턴] Chapter 4-4. Builder Pattern : 장단점 (0) | 2022.05.16 |
---|---|
[객체 생성 패턴] Chapter 4-3. Builder Pattern : 심플 빌더 (0) | 2022.05.12 |
[객체 생성 패턴] Chapter 4-1. Builder Pattern : 패턴 소개 (0) | 2022.05.08 |
[디자인 패턴] 구상 클래스(Concrete class)란? (0) | 2022.05.08 |
[객체 생성 패턴] Chapter 3-4. Abstract Factory Pattern : Java와 Spring에서 찾아보는 추상 팩토리 패턴 (0) | 2022.04.26 |