빌더, 생성 패턴 중 하나
복잡하게 많은 것을 요구하는 객체를 생각해보면, 이 객체를 만들어낼 때는 복잡하고, 많은 파라메타가 있는 constructor가 요구된다. 예를 들어서, House
객체를 만드는 것을 가정해보자. 간단한 집을 짓기 위해서는 4면의 벽, 바닥, 문, 딱 맞는 창문들, 지붕을 만들어야 한다. 그런데 추가적으로 뒷마당, 다른 굿즈들 등을 설정하려면 어떻게 해야 할까? 단순히 기본이되는 House
클래스를 확장하는 것은 수 많은 수정사항을 야기할 것이다. (subclass들을 모두 수정해야 함) 다른 방법으로 constructor를 방대하게 짜는 것인데, 이것은 constructor call을 아주 못생기고 낭비스럽게 해야한다는 문제가 있다.
// 못 생긴 constructor 호출
// House(doors, windows, rooms, garage, swimPool, ...)
const house1 = new House(2, 4, 4, null, null, null, ...);
const house2 = new House(1, 3, 2, true, null, true, ...);
빌더 패턴은 객체를 구성하는 코드를 클래스에서 분리시키고 builder에 따로 옮기는 방법이다. 빌더 패턴에서는 원하는 객체를 만들기 위해서 몇 개의 단계를 빌더 오브젝트에서 실행해야 한다. 중요한 점은 모든 단계를 수행할 필요가 없다는 것이다.
Builder
: Product 객체의 일부 요소들을 생성하기 위한 추상 인터페이스를 정의한다.ConcreteBuilder
: Builder 클래스에 정의된 인터페이스를 구현하며, 제품의 부품들을 모아 빌더를 복합한다. 생성한 요소의 표현을 정의하고 관리한다. 또한 제품을 검색하는데 필요한 인터페이스를 제공한다.Director
: Builder 인터페이스를 사용하는 객체를 합성한다. 이름 그대로 감독과 같은 느낌Product
: 생성할 복합 객체를 표현한다.Director
객체를 생성하고, 생성한 객체를 자신이 원하는 Builder
객체로 합성해 나감.Director
는 Builder
에 통보한다.Builder
는 Director
의 요청을 처리하여 제품에 부품을 추가한다.제품 내부 표현을 다양하게 변화할 수 있다.
- 빌더를 사용하면 제품이 어떤 요소에서 복합되는지, 각 요소들의 표현 방법이 무엇인지 가릴 수 있게 된다.
생성과 표현에 필요한 코드를 분리한다.
- 사용자가 빌더와 상호작용을 통해 필요한 복합 객체를 생성하게 된다.
복합 객체를 생성하는 절차를 좀 더 세밀하게 나눌 수 있다.
interface HouseBuilder {
produceDoor(): void;
produceWindow(): void;
produceBackyard(): void;
produceSwimPool(): void;
}
class ConcreteHouseBuilder implements HouseBuilder {
private house: House = new House();
public reset() {
this.house = new House();
}
produceBackyard(): void {
this.house.parts = "Back yard";
}
produceDoor(): void {
this.house.parts = "2 Doors";
}
produceSwimPool(): void {
this.house.parts = "Swim Pool";
}
produceWindow(): void {
this.house.parts = "4 Windows";
}
getHouse(): House {
const result = this.house;
this.reset();
return result;
}
}
class House {
private _parts: string[] = [];
get parts(): string {
return `The House with ${this._parts.join(", ")}`;
}
set parts(part: string) {
this._parts.push(part);
}
}
class HouseDirector {
private builder: HouseBuilder;
constructor(builder: HouseBuilder) {
this.builder = builder;
}
buildMinimalHouse(): void {
this.builder.produceDoor();
this.builder.produceWindow();
}
buildFullFeaturedHouse(): void {
this.builder.produceBackyard();
this.builder.produceSwimPool();
this.builder.produceDoor();
this.builder.produceWindow();
}
}
const clientCode = (): void => {
const builder = new ConcreteHouseBuilder();
const director = new HouseDirector(builder);
console.log("Full Featured House: ");
director.buildFullFeaturedHouse();
const FullFeaturedHouse = builder.getHouse();
console.log(FullFeaturedHouse.parts);
console.log("");
console.log("Minial House: ");
director.buildMinimalHouse();
const minimalHouse = builder.getHouse();
console.log(minimalHouse.parts);
console.log("");
console.log("Custom House: ");
builder.produceBackyard();
builder.produceDoor();
const customHouse = builder.getHouse();
console.log(customHouse.parts);
};
clientCode();
Full Featured House:
The House with Back yard, Swim Pool, 2 Doors, 4 Windows
Minial House:
The House with 2 Doors, 4 Windows
Custom House:
The House with Back yard, 2 Doors
Done in 0.15s.