[디자인 패턴] 빌더(Builder) 패턴

❓빌더 패턴이 무엇일까?

빌더 패턴은 복합 객체 생성 과정을 별도의 독립된 클래스로 관리하는 패턴을 말합니다. 쉽게 말해 build라는 클래스 하나로 생성을 모두 관리할 수 있다는 건데요. 그렇다면 복합 객체, 복잡한 객체란 무엇을 뜻하는 것일까요?

❓복합 객체?

복합 객체는 내부적으로 다른 클래스의 객체를 포함하고 있는 것을 말합니다.

// Door 클래스 정의
class Door {
  operate(action) {
    console.log(`The door is ${action}.`);
  }
}

// Car 클래스 정의
class Car {
  constructor(model, year) {
    this.model = model;
    this.year = year;
    this.doors = [];
  }

  addDoor(color) {
    const newDoor = new Door();
    this.doors.push(newDoor);
  }

  operateAllDoors(action) {
    this.doors.forEach((door) => {
      door.operate(action);
    });
  }
}

이런 식으로 다른 클래스에 의존하게 되는데요, 이 복합 객체가 많아질 수록, 추상 메서드 패턴으로는 객체를 생성하기가 어려워집니다. 기존 생성 패턴의 한계로 빌더 패턴이 탄생하게 되었다는 것이죠.

❗빌더 패턴을 알아보자

빌더 패턴이 복합 객체를 관리하는데 좋은 패턴이라는 것을 알았습니다. 그렇다면 어떤식으로 동작하는 것일까요?

빌더 패턴에서 눈에 보이는 것은, ‘빌더’와 ‘알고리즘’입니다.

빌더는 추상화를 통해 복합 객체를 생성 관리 할 수 있는데요, 이렇게 만들어진 하위 클래스를 ConcreteBuilder라고 합니다.

알고리즘 역시 추상화를 통해서 모델의 생성 관리를 할 수 있습니다.

JS 코드를 통해 알아봅시다.

// Product: 자동차 객체
class Car {
  constructor() {
    this.make = '';
    this.model = '';
    this.year = '';
  }

  describe() {
    console.log(`Car: ${this.year} ${this.make} ${this.model}`);
  }
}

// Builder: 자동차 객체를 생성하기 위한 빌더 인터페이스
class CarBuilder {
  constructor() {
    this.car = new Car();
  }

  setMake(make) {
    this.car.make = make;
    return this;
  }

  setModel(model) {
    this.car.model = model;
    return this;
  }

  setYear(year) {
    this.car.year = year;
    return this;
  }

  build() {
    return this.car;
  }
}

const sportCar = new Builder().setMake('Ferrari').setModel('458').setYear('2023').build();

코드에서는 빌더 객체 sportCar라는 객체를 만들었습니다. 간단한 예시여서 추상화와 알고리즘을 분리하지 않았지만, 이렇게 builder를 통해 여러 객체를 만들 수 있다는 것입니다!

추상 팩토리와 비슷하게 보일 수 있지만, 추상 팩토리는 서브 단계에서 생성 절차가 완료되면 각각의 모델을 바로 반환하는데 반면, 빌더 패턴은 단계별 생성 절차가 모두 완료 되어야 복합 객체를 반환한다는 것입니다.

즉 추상 패턴은 각각의 부품에 의미를 부여하는 반면, 빌더 패턴은 부품이 전부 모여야 의미를 가진다는 것!

❓빌더 패턴의 단점

빌더 패턴은 알고리즘 덕분에 교환 가능성, 그리고 객체군 사이의 의존성을 없앨 수 있어 복잡성을 관리하기 편리합니다.

하지만 이는 작은 규모의 객체나 단순한 객체 생성에는 불필요한 작업일 수 있습니다. 이에 자원 낭비, 오버헤드가 발생할 수 있고, 오히려 유지보수에 어려윰을 가져다 줄 수 있습니다.

그러니 작은 규모의 객체나 생성 로직이 단순한 경우에는 사용하지 않는 것이 좋겠네요.