Builder[Design Pattern]

SnowCat·2023년 2월 23일
0

Design Pattern

목록 보기
4/23
post-thumbnail

의도

  • 복잡한 개체들을 단계적으로 생성할 수 있도록 하는 생성 디자인 패턴
  • 같은 제작 코드를 사용하여 객체의 다양한 유형들과 표현 제작 가능

문제

  • 집을 짓고 싶다 해보자. 집 자체의 클래스는 이미 구현이 되어있다.
  • 그런데 정원, 차고, 수영장 등을 조합하고자 한다. 제일 간단한 방법은 모든 경우의 수를 조합한 클래스를 각각 생성하는 것이다.
  • 그런데 이렇게 되면 객체만 16개 조합이 나오게 된다. 다른 방법으로는 자식클래스 대신 생성자에 매개변수 조합을 사용할수도 있을 것이다.
  • 코드의 양은 확실히 줄어들 것이다. 하지만 매개변수의 갯수가 많아지면 생성자 코드가 복잡해지게 될것이다.

해결책

  • 빌더 패턴을 사용해 클래스에서 객체 생성 코드를 추출하여 별도의 객체들로 분리가 필요함
  • 객체 생성 단계를 빌더에 정리하며, 객체 생성시 빌더 객체를 호출하면 됨
  • 객체의 필요에 맞제 필요한 부분만 실행할 수 있음
  • 필요하다면 객체를 생성하는데 사용하는 빌더 단계들에 대한 일련의 호출을 디렉터(관리자) 라는 별도의 클래스로 구현 가능
    디렉터 클래스는 제작 단계들을 실행하는 순서를 정의하고, 빌더는 단계들에 대한 구현을 정의함

구조

// 빌더 -> 객체 생성에 필요한 파트들을 정의하는 인터페이스
interface Builder {
  	reset():void
    producePartA(): void;
    producePartB(): void;
    producePartC(): void;
}

// 항상 실제 구현은 Concrete Builder에서 실행
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;
	
  	// 사용할 빌더를 정하는 setter
    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);

  /*
  Standard basic product:
  Product parts: PartA1
  */
    console.log('Standard basic product:');
    director.buildMinimalViableProduct();
    builder.getProduct().listParts();

  /*
  Standard full featured product:
  Product parts: PartA1, PartB1, PartC1
  */
    console.log('Standard full featured product:');
    director.buildFullFeaturedProduct();
    builder.getProduct().listParts();

  // 디렉터 없이 빌더 단독으로도 객체 생성 가능
  /*
  Custom product:
  Product parts: PartA1, PartC1
  */
    console.log('Custom product:');
    builder.producePartA();
    builder.producePartC();
    builder.getProduct().listParts();
}

const director = new Director();
clientCode(director);

적용

  • 점층적 생성자를 없에기 위해 사용
    점층적 생성자: 매개변수가 매우 많을 때 메서드 오버로딩을 사용해 여러개의 생성자를 사용하는 방법
    빌더 패턴을 사용하면 필요한 단계들만 꺼내서 사용하면 됨
  • 생성 방법은 비슷하지만 내용물만 다른 객체를 생성할 때 사용
    인터페이스에 가능한 모든 생성 단계를 정의하고 실제 빌더 클래스에서는 여러 변형된 객체들을 만들기만 하면 됨
  • 복잡한 객체들을 생성할 때 사용
    제품을 단계적으로 원하는 부분들만 조립해나가면서 생성할 수 있고 최종 제품을 손상하지 않고 일부 단계를 연기할 수 있음

구현 방법

  1. 사용하는 모든 제품을 생성하기 위한 공통 생성 단계를 구성할 수 있는지 확인 (이게 안되면 빌더 패턴 사용 불가능)
  2. 기초 인터페이스에서 공통 생성 단계 선언
  3. 각 제품에 대한 구상 빌더 클래스를 만들고 인터페이스 구현, 이때 객체를 가져오는 메서드도 추가해야 함
  4. 생성 과정을 캡슐화 하고 싶으면 디렉터 생성도 고려할 필요가 있음
  5. 클라이언트 객체는 빌더 객체와 (있으면) 디렉터 객체를 모두 생성해야 함
  6. 디렉터 객체가 없으면 빌더의 메서드를 사용해 객체를 얻고, 있으면 객체에 빌더를 전달해 객체를 얻어냄
    모든 객체가 같은 인터페이스를 따라야 디렉터에서 직접 생성 결과를 얻을 수 있고, 아니면 빌더에서 결과를 얻어내야 함

장단점

  • 객체들을 단계적으로 생성하고, 생성 단계를 연기하고, 재귀적으로 단계를 실행할 수 있음
  • 객체의 변형을 만들 때 같은 생성 코드를 재사용할 수 있음
  • 제품의 비지니스 로직에서 생성 코드를 분리해 단일 책임 원칙 준수
  • 패턴이 여러개의 새 클래스를 생성해야 하기에 코드의 복잡성이 증가함

출처:
https://refactoring.guru/ko/design-patterns/builder

profile
냐아아아아아아아아앙

0개의 댓글