본문 바로가기
필수 개발지식/디자인패턴

[Design Pattern] 빌더패턴 (Builder)

by 코딩하는짱구 2023. 5. 10.
반응형

✅디자인패턴이 뭘까?

 

" 디자인 패턴(Design Pattern)은 소프트웨어 디자인에서 주어진 컨텍스트 내에서

일반적으로 발생하는 문제에 대한 재사용 가능한 일반적인 솔루션"

 

 


디자인패턴은 왜 쓰이나요?

유연성 : 디자인 패턴을 사용하면 코드가 유연해지며 개체가 서로 느슨하게 결합되어 코드를 쉽게 변경할 수 있으므로 올바른 수준의 추상화를 제공하는데 도움이 된다.

재사용성 : 느슨하게 결합되고 결합된 객체 및 클래스는 코드를 더 재사용할 수 있게 만들며 이러한 종류의 코드는 고도로 결합된 코드에 비해 테스트 하기 쉽다.

공유 어휘 : 공유 어휘를 사용하면 코드와 생각을 다른 팀원과 쉽게 공유할 수 있으며 코드와 관련된 팀원 간의 이해를 높일 수 있다.

모범 사례 캡처 : 이미 시도되고 검증된 해결책이기에 이 패턴을 통해 많은 종류의 문제를 해결할 수 있다.

 

 


디자인패턴엔 뭐가있나요?

생성(Creational) 패턴 구조(Structural) 패턴 행위(Behavioral) 패턴
추상 팩토리(Abstract Factory)
빌더(Builder)
팩토리 메서드(Factory Method)
프로토 타입(Prototype)
싱글턴(Singleton)
어댑터(Adapter)
브릿지(Bridge)
컴퍼지트(Composite)
데커레이터(Decorator)
퍼사드(FaA§ade)
플라이웨이트(Flyweight)
프록시(Proxy)
체인 오브 리스폰스빌리티
(Chain of Responsibility)
커맨드(Command)
인터프리터(Interpreter)
이터레이터(Iterator)
미디에이터(Mediator)
메멘토(Memento)
옵저버(Observer)
스테이트(State)
스트래티지(Strategy)
템플릿 메서드(Template Method)
비지터(Visitor)

 

1. 생성(Creational) 패턴

   생성 디자인 패턴은 개체의 인스턴스화 프로세스를 디자인하는데 사용된다.

 

2. 구조(Structural) 패턴

    클래스나 객체를 조합해 더 큰 구조를 만드는 패턴이다.
    예를 들어 서로 다른 인터페이스를 지닌 2개의 객체를 묶어 단일 인터페이스를 제공하거나 객체들을 서로 묶어 새로운 기능을 제공하는 패턴이다.

3. 행위(Behavioral) 패턴

  객체나 클래스 사이의 알고리즘이나 책임 분배에 관련된 패턴이다.

  한 객체가 혼자 수행할 수 없는 작업을 여러 개의 객체로 어떻게 분배하는지, 또 그렇게 하면서도 객체 사이의 결합도를  최소화하는것에 중점을 둔다.

 

 

✅생성패턴의 빌더(builder)에 대해 알아보자!

 


빌더패턴이란?

결론부터 말하면 복잡한 객체들을 단계별로 생성할 수 있도록 하는 패턴!!

 

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

 

위의 그림과 같이 [동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법]으로, 

복잡한 구성의 객체를 효과적으로 생성하는 패턴이다. 이 그림만 봐서는 무슨 말인지 단번에 파악하기 힘드니 아래로~~ 

 


빌더패턴은 왜 쓰이나요?

이런 식으로 다양한 종류의 집을 건설하고 싶을때, 

 

 

보통 대부분의 매개변수가 사용되지 않아 생성자 호출들의 코드가 매우 못생겨질 것이다. 예를 들어, 극소수의 집들에만 수영장이 있으므로 수영장과 관련된 매개변수들은 십중팔구 사용되지 않을 것이다.

 


빌더패턴은 어떻게 쓰이나요?

 

 

우리가 Car라는 자동차 객체를 생성한다고 생각해보자.

//빌더를 사용하지 않았을 경우

class Car {
  constructor(brand, color, price) {
    this.brand = brand;
    this.color = color;
    this.price = price;
  }

  getInfo() {
    return `Brand: ${this.brand}, Color: ${this.color}, Price: ${this.price}`;
  }
}


const car = new Car('Tesla', 'Black', 50000);

console.log(car.getInfo()); // Brand: Tesla, Color: Black, Price: 50000

 

Car 객체는 생성자 파라미터로 brand, color, price을 순서대로 받는다. 

이 경우 몇가지 불편한 점이 있다. 

  • 선택적 속성 설정의 어려움: 선택적으로 속성을 설정하려면 매개변수를 생략하고 기본값을 설정해야함, 이 경우엔 매번 파라미터 순서를 체크해야함. 
  • 매개변수가 많은 생성자: 파라미터의 개수가 늘어나면 코드를 읽기 어렵게 만들 수 있고, 순서를 실수로 바꿀 가능성도 커짐.
  • 코드를 처음보는 사람은 어떤 값이 어떤 속성에 대응되는지 파악할 수 없다. 

 

위의 문제를 코드로 보면 이해가 빠른데, 

예를 들어 Car라는 객체에 브랜드명, 컬러, 가격이 아닌 브랜드명, 가격만 '선택적으로' 넣고싶다고 가정했을 때 아래와 같이 작성하면 기본값이 없는 경우 매개변수가 정의되지않아 출력할 수가 없다.

const car = new Car('Tesla', 'Black', 50000); //모든 파라미터를 입력
const car2 = new Car('Benz', 'White'); //파라미터를 선택적으로 넣었을 경우

console.log(car.getInfo()); // Brand: Tesla, Color: Black, Price: 50000
console.log(car2.get());

 

또한 

const car = new Car('Tesla', 'Black', 50000);

만 보고서는 'Tesla'가 'brand'속성에 대응되고, 'Black'이 'color'속성에 대응되며, '50000'이 'price'속성에 대응되는지 알 수 없다. 

 

 

 

이런 불편함을 해결하기 위해! 

아래와 같이 builder를 사용해서 코드를 다시 짜보면,

//빌더를 사용한 경우

class Car {
  constructor() {
    this.brand = '';
    this.color = '';
    this.price = 0;
  }

  setBrand(brand) {
    this.brand = brand;
  }

  setColor(color) {
    this.color = color;
  }

  setPrice(price) {
    this.price = price;
  }

  getInfo() {
    return `Brand: ${this.brand}, Color: ${this.color}, Price: ${this.price}`;
  }
}

class CarBuilder {
  constructor() {
    this.car = new Car();
  }

  setBrand(brand) {
    this.car.setBrand(brand); 
    return this;
  }

  setColor(color) {
    this.car.setColor(color);
    return this;
  }

  setPrice(price) {
    this.car.setPrice(price);
    return this;
  }

  build() {
    return this.car;
  }
}

// Builder를 사용하여 자동차 객체를 생성, this를 반환하여 메서드 체이닝을 허용한다!
const car1 = new CarBuilder()
  .setBrand('Tesla')
  .setColor('Black')
  .setPrice(50000)
  .build();

const car2 = new CarBuilder().setColor('White').setPrice(20000).build();

console.log(car1.getInfo()); // Brand: Tesla, Color: Black, Price: 50000
console.log(car2.getInfo()); // Color: White, Price: 20000 //선택적인 정보로 객체를 지정할 수 있다!

위의 코드에서 'CarBuilder' 클래스는 자동차 객체를 생성하기 위한 Builder 이다. 

setBrand, setColor, setPrice와 같은 메서드를 제공하여 자동차의 속성을 선택적으로 설정할 수 있다. 

 

 

//builder 적용 전

const car = new Car('Tesla', 'Black', 50000);

//builder 적용 후

const car = new CarBuilder()
  .setBrand('Tesla')
  .setColor('Black')
  .setPrice(50000)
  .build();

 

위의 코드를 보면 각 메서드 호출이 어떤 속성을 설정하는지 명확하게 알 수 있다. 이런 식으로 **메서드 체이닝을 사용하면 속성의 설정 과정이 명확하게 드러난다. 즉 객체 생성 과정을 단계별로 나눌 수 있고, 필요한 속성만 설정할 수 있으며 유연성이 증가한다. 

 

 

 

✅생성패턴의 빌더(builder)의 단점은 없나?

단점도 물론 존재한다. 

  • new Car("브랜드",  "컬러",  "가격",  "연식",  "주유량", 등등 등등 )과 같이 호출이 많아질 경우 그만큼 생성자를 많이 만들어야 한다. (객체의 프로퍼티가 많으면 그에 따른 생성자도 많이 만들어야)
  • 복잡한 객체의 경우 여러 단계의 호출과 설정이 필요하므로 코드가 길어지고 복잡해진다.
  • 코드가 복잡하고 길어짐에 따라 가독성이 저하된다. 

이러한 단점들을 고려하며 Builder 패턴을 적용할지 여부를 결정해야 한다!

 

 

 

 

 

결국

'Builder'라는 것은복잡한 객체들을 단계별로 생성할 수 있도록 하는 디자인패턴.

 

 

 

 

 

 

**메서드 체이닝이란? 

obj.method1();
obj.method2();
obj.method3();

obj.method1().method2().method3();

반응형