의도
- 복잡한 개체들을 단계적으로 생성할 수 있도록 하는 생성 디자인 패턴
- 같은 제작 코드를 사용하여 객체의 다양한 유형들과 표현 제작 가능
문제
- 집을 짓고 싶다 해보자. 집 자체의 클래스는 이미 구현이 되어있다.
- 그런데 정원, 차고, 수영장 등을 조합하고자 한다. 제일 간단한 방법은 모든 경우의 수를 조합한 클래스를 각각 생성하는 것이다.
- 그런데 이렇게 되면 객체만 16개 조합이 나오게 된다. 다른 방법으로는 자식클래스 대신 생성자에 매개변수 조합을 사용할수도 있을 것이다.
- 코드의 양은 확실히 줄어들 것이다. 하지만 매개변수의 갯수가 많아지면 생성자 코드가 복잡해지게 될것이다.
해결책
- 빌더 패턴을 사용해 클래스에서 객체 생성 코드를 추출하여 별도의 객체들로 분리가 필요함
- 객체 생성 단계를 빌더에 정리하며, 객체 생성시 빌더 객체를 호출하면 됨
- 객체의 필요에 맞제 필요한 부분만 실행할 수 있음
- 필요하다면 객체를 생성하는데 사용하는 빌더 단계들에 대한 일련의 호출을 디렉터(관리자) 라는 별도의 클래스로 구현 가능
디렉터 클래스는 제작 단계들을 실행하는 순서를 정의하고, 빌더는 단계들에 대한 구현을 정의함
구조
interface Builder {
reset():void
producePartA(): void;
producePartB(): void;
producePartC(): void;
}
class ConcreteBuilder1 implements Builder {
private product: Product1;
constructor() {
this.reset();
}
public reset(): void {
this.product = new Product1();
}
public producePartA(): void {
this.product.parts.push('PartA1');
}
public producePartB(): void {
this.product.parts.push('PartB1');
}
public producePartC(): void {
this.product.parts.push('PartC1');
}
public getProduct(): Product1 {
const result = this.product;
this.reset();
return result;
}
}
class Product1 {
public parts: string[] = [];
public listParts(): void {
console.log(`Product parts: ${this.parts.join(', ')}\n`);
}
}
class Director {
private builder: Builder;
public setBuilder(builder: Builder): void {
this.builder = builder;
}
public buildMinimalViableProduct(): void {
this.builder.producePartA();
}
public buildFullFeaturedProduct(): void {
this.builder.producePartA();
this.builder.producePartB();
this.builder.producePartC();
}
}
function clientCode(director: Director) {
const builder = new ConcreteBuilder1();
director.setBuilder(builder);
console.log('Standard basic product:');
director.buildMinimalViableProduct();
builder.getProduct().listParts();
console.log('Standard full featured product:');
director.buildFullFeaturedProduct();
builder.getProduct().listParts();
console.log('Custom product:');
builder.producePartA();
builder.producePartC();
builder.getProduct().listParts();
}
const director = new Director();
clientCode(director);
적용
- 점층적 생성자를 없에기 위해 사용
점층적 생성자: 매개변수가 매우 많을 때 메서드 오버로딩을 사용해 여러개의 생성자를 사용하는 방법
빌더 패턴을 사용하면 필요한 단계들만 꺼내서 사용하면 됨
- 생성 방법은 비슷하지만 내용물만 다른 객체를 생성할 때 사용
인터페이스에 가능한 모든 생성 단계를 정의하고 실제 빌더 클래스에서는 여러 변형된 객체들을 만들기만 하면 됨
- 복잡한 객체들을 생성할 때 사용
제품을 단계적으로 원하는 부분들만 조립해나가면서 생성할 수 있고 최종 제품을 손상하지 않고 일부 단계를 연기할 수 있음
구현 방법
- 사용하는 모든 제품을 생성하기 위한 공통 생성 단계를 구성할 수 있는지 확인 (이게 안되면 빌더 패턴 사용 불가능)
- 기초 인터페이스에서 공통 생성 단계 선언
- 각 제품에 대한 구상 빌더 클래스를 만들고 인터페이스 구현, 이때 객체를 가져오는 메서드도 추가해야 함
- 생성 과정을 캡슐화 하고 싶으면 디렉터 생성도 고려할 필요가 있음
- 클라이언트 객체는 빌더 객체와 (있으면) 디렉터 객체를 모두 생성해야 함
- 디렉터 객체가 없으면 빌더의 메서드를 사용해 객체를 얻고, 있으면 객체에 빌더를 전달해 객체를 얻어냄
모든 객체가 같은 인터페이스를 따라야 디렉터에서 직접 생성 결과를 얻을 수 있고, 아니면 빌더에서 결과를 얻어내야 함
장단점
- 객체들을 단계적으로 생성하고, 생성 단계를 연기하고, 재귀적으로 단계를 실행할 수 있음
- 객체의 변형을 만들 때 같은 생성 코드를 재사용할 수 있음
- 제품의 비지니스 로직에서 생성 코드를 분리해 단일 책임 원칙 준수
- 패턴이 여러개의 새 클래스를 생성해야 하기에 코드의 복잡성이 증가함
출처:
https://refactoring.guru/ko/design-patterns/builder