빌더 패턴
- 일괄된 프로세스로 다양한 구성의 인스턴스를 만드는 패턴
투어 일정 생성
- 투어 일정을 생성하는 코드를 구현하며 패턴을 이해해보자
class TourPlan {
#title: string | undefined;
#nights: number | undefined;
#days: number | undefined;
#startDate: Date | undefined;
#whereToStay: string | undefined;
#plans: DetailPlan[] | undefined;
get title() {
return this.#title;
}
set title(v: string | undefined) {
this.#title = v;
}
get nights() {
return this.#nights;
}
set nights(v: number | undefined) {
this.#nights = v;
}
get days() {
return this.#days;
}
set days(v: number | undefined) {
this.#days = v;
}
get startDate() {
return this.#startDate;
}
set startDate(v: Date | undefined) {
this.#startDate = v;
}
get whereToStay() {
return this.#whereToStay;
}
set whereToStay(v: string | undefined) {
this.#whereToStay = v;
}
get plans() {
return this.#plans;
}
set plans(v: DetailPlan[] | undefined) {
this.#plans = v;
}
addPlan(day: number, plan: string) {
if (this.#plans) {
this.#plans.push(new DetailPlan(day, plan));
} else {
this.#plans = [new DetailPlan(day, plan)];
}
}
}
class DetailPlan {
#day: number;
#plan: string;
constructor(day: number, plan: string) {
this.#day = day;
this.#plan = plan;
}
get day() {
return this.#day;
}
get plan() {
return this.#plan;
}
}
const tourPlan = new TourPlan();
tourPlan.title = '칸쿤여행';
tourPlan.nights = 2;
tourPlan.days = 3;
tourPlan.startDate = new Date(2021, 3, 10);
tourPlan.whereToStay = '리조트';
tourPlan.addPlan(0, '체크인 후 짐풀기');
tourPlan.addPlan(0, '저녁 식사');
tourPlan.addPlan(1, '조식 뷔페에서 식사');
tourPlan.addPlan(1, '해변가 산책');
tourPlan.addPlan(1, '점심은 수영장 근처 음식점에서 먹기');
tourPlan.addPlan(1, '리조트 수영장에서 놀기');
tourPlan.addPlan(1, '저녁은 BBQ 식당에서 스테이크');
tourPlan.addPlan(2, '조식 뷔페에서 식사');
tourPlan.addPlan(2, '체크아웃');
const shortPlan = new TourPlan();
shortPlan.title = '오레곤 롱비치 여행';
shortPlan.startDate = new Date(2021, 4, 15);
코드의 문제점
- tourPlan 과 shortPlan 을 만들었는데, 만들 때 일괄된 프로세스가 없다.
- 투어 일정을 만들 때 속성을 설정하는 자유도가 너무 높아서 불완전한 객체가 만들어질 위험이 있다
ex) 2박 3일 일정인데, nights 속성은 2 로 설정하고 days 속성은 설정하지 않은 경우
- 투어 종류에 따라 필요한 속성들을 생성자를 통해서 강제하려고 할 경우, 생성자의 수가 장황하게 많아질 수 있다
빌더 패턴 적용
interface TourPlanBuilder {
title(title: string): TourPlanBuilder;
nightsAndDays(nights: number, days: number): TourPlanBuilder;
startDate(date: Date): TourPlanBuilder;
whereToStay(whereToStay: string): TourPlanBuilder;
addPlan(day: number, plan: string): TourPlanBuilder;
getPlan(): TourPlan;
}
interface TourPlanConstructor {
title?: string;
nights?: number;
days?: number;
startDate?: Date;
whereToStay?: string;
plans?: DetailPlan[];
}
export class TourPlan {
#title: string | undefined;
#nights: number | undefined;
#days: number | undefined;
#startDate: Date | undefined;
#whereToStay: string | undefined;
#plans: DetailPlan[] | undefined;
constructor(args?: TourPlanConstructor) {
this.#title = args?.title;
this.#nights = args?.nights;
this.#days = args?.days;
this.#startDate = args?.startDate;
this.#whereToStay = args?.whereToStay;
this.#plans = args?.plans;
}
}
class DefaultTourBuilder implements TourPlanBuilder {
#title: string | undefined;
#nights: number | undefined;
#days: number | undefined;
#startDate: Date | undefined;
#whereToStay: string | undefined;
#plans: DetailPlan[] | undefined;
title(title: string): TourPlanBuilder {
this.#title = title;
return this;
}
nightsAndDays(nights: number, days: number): TourPlanBuilder {
this.#nights = nights;
this.#days = days;
return this;
}
startDate(date: Date): TourPlanBuilder {
this.#startDate = date;
return this;
}
whereToStay(whereToStay: string): TourPlanBuilder {
this.#whereToStay = whereToStay;
return this;
}
addPlan(day: number, plan: string): TourPlanBuilder {
if (this.#plans) {
this.#plans.push(new DetailPlan(day, plan));
} else {
this.#plans = [new DetailPlan(day, plan)];
}
return this;
}
getPlan(): TourPlan {
return new TourPlan({
title: this.#title,
nights: this.#nights,
days: this.#days,
startDate: this.#startDate,
whereToStay: this.#whereToStay,
plans: this.#plans,
});
}
}
const cancunTrip: TourPlan = new DefaultTourBuilder()
.title('칸쿤 여행')
.nightsAndDays(2, 3)
.startDate(new Date(2021, 3, 10))
.whereToStay('리조트')
.addPlan(0, '체크인 후 짐풀기')
.addPlan(0, '저녁 식사')
.addPlan(1, '조식 뷔페에서 식사')
.addPlan(1, '해변가 산책')
.addPlan(1, '점심은 수영장 근처 음식점에서 먹기')
.addPlan(1, '리조트 수영장에서 놀기')
.addPlan(1, '저녁은 BBQ 식당에서 스테이크')
.addPlan(2, '조식 뷔페에서 식사')
.addPlan(2, '체크아웃')
.getPlan();
const longBeachTrip: TourPlan = new DefaultTourBuilder()
.title('오레곤 롱비치 여행')
.startDate(new Date(2021, 4, 15))
.getPlan();
- 빌더 패턴을 적용함으로써 메서드 체이닝을 통해 일괄된 프로세스를 만들었다
- 함께 설정되어야 하는 속성들을 한 메서드를 통해 관리하므로써 불완전한 객체의 생성을 방지했다
한번 더 래핑
- 자주 반복되어 만들어지는 투어 일정들은 한번 더 래핑할 수 있다.
class TourDirector {
#tourPlanBuilder: TourPlanBuilder;
constructor(tourPlanBuilder: TourPlanBuilder) {
this.#tourPlanBuilder = tourPlanBuilder;
}
cancunTrip(): TourPlan {
return this.#tourPlanBuilder
.title('칸쿤 여행')
.nightsAndDays(2, 3)
.startDate(new Date(2021, 3, 10))
.whereToStay('리조트')
.addPlan(0, '체크인 후 짐풀기')
.addPlan(0, '저녁 식사')
.addPlan(1, '조식 뷔페에서 식사')
.addPlan(1, '해변가 산책')
.addPlan(1, '점심은 수영장 근처 음식점에서 먹기')
.addPlan(1, '리조트 수영장에서 놀기')
.addPlan(1, '저녁은 BBQ 식당에서 스테이크')
.addPlan(2, '조식 뷔페에서 식사')
.addPlan(2, '체크아웃')
.getPlan();
}
longBeachTrip(): TourPlan {
return this.#tourPlanBuilder
.title('오레곤 롱비치 여행')
.startDate(new Date(2021, 4, 15))
.getPlan();
}
}
const director = new TourDirector(new DefaultTourBuilder());
const cancunTrip: TourPlan = director.cancunTrip();
const longBeachTrip: TourPlan = director.longBeachTrip();