본문 바로가기
Javascript/Node.js

Builder Pattern - 생성자 디자인 패턴

by v8rus 2022. 2. 3.

빌더

"유창한 인터페이스를 제공, 복잡한 객체의 생성을 단순화"

단계별로 객체를 만들 수 있음 => 가독성, 일반적인 개발자 사용성 향상

가장 좋은 상황 : 인자의 목록이 길거나, 많은 복잡한 매개 변수를 입력해서 사용하는 생성자가 있는 클래스

 

class Boat {
  constructor (
    hasMotor, motorCount, mototBrand, motorModel,
    hasSails, sailsCount, sailsMaterial, sailsColor,
    ... 
  )
}

const myBoat = new Boat(ture, 2, 'Best Motor Co.', 'OM123', true, 1, 'fabric', 'white', 'blue', false);

어쩔거야 이거, 맨날 칠거니?

오류가 발생하기 쉽고, 인수를 알아보기 어려움


1단계 해결방법 : 모든 인자를 하나의 객체 리터럴에 모음

class Boat {
  constructor (allParameters) {
    // ...
  }
}

const myBoat = new Boat({
  hasMotor : true,
  motorCount : 2,
  mototBrand : 'Best Motor Co. ',
  motorModel : 'OM123',
  // ...
})

매개 변수값이 무엇인지 명확하게 알 수 있음
다만 실제 입력이 무엇인지 알기 위해 클래스 정의 문서, 클래스 코드를 봐야 함

 

2단계 해결방법일관된 클래스를 생성하도록 강제
Ex) hasMotor : true 이면 [ motorCount, motorBrand, motorModel ] 지정 강제화

 

빌더 패턴은 작은 결함을 교정하고 읽기 쉬우며 자체 문서화 가능 => 일관된 객체 생성을 위한 지침 제공

class BoatBuilder {
  withMotors (count ,brand, model) {
    this.hasMotor = true;
    this.motorCount = count;
    this.motorBrand = brand;
    this.motorModel = model;

    return this;
  }

  withSails (count, material, color) {
    this.hasSails = true;
    this.sailsColor = count;
    this.sailsMaterial = material;
    this.sailsColor = color;

    return this;
  }

  hullcolor (color) {
    this.hullcolor = color;

    return this;
  }

  withCabin () {
    this.hasCabin = true;

    return this;
  }

  build () {
    return new Boat({
      hasMotor : this.hasMotor,
      motorCount : this.motorCount,
      motorBrand : this.motorBrand,
      motorModel : this.motorModel,
      hasSails : this.hasSails,
      sailsCount : this.sailsCount,
      sailsMaterial : this.sailsMaterial,
      sailsColor : this.sailsColor,
      hullcolor : this.hullcolor,
      hasCabin : this.hasCabin,
    })
  }
}

// 사용 예시
const myBoat = new BoatBuilder()
  .withMotors(2, 'Best Motor Co.' , 'OM123')
  .withSails(1, 'fabric', 'white')
  .withCabin()
  .hullcolor('blue')
  .build();

BoatBuilder 클래스의 역할은 일부 헬퍼 함수를 사용하여  Boat 생성에 필요한 모든 매게변수를 모음

빌더 패턴 구현하기 위한 일반적인 규칙

  • 주요 목적은 복잡한 생성자를 더 읽기 쉽고 관리가 쉬운 여러가지 단계로 나눔
  • 한번에 관련된 여러 매개변수들을 설정할 수 있는 빌더 함수를 만듦
  • Setter 함수를 통해 입력받을 값이 무엇인지 명확히 하고, 빌더 인터페이스를 사용하는 사용자가 알 필요가 없는 파라미터를 세팅하는 더 많은 로직을 setter함수 내에 캡슐화
  • 필요 시 형 변환, 정규화, 유효성 검사와 같은 조작을 추가, 빌더 클래스를 사용할때 수행 해야 하는 작업을 단순화

함수를 호출 할 때도 적용 할 수 있음

 

 

1. URL 객체 빌더 구현

* 실제로 쓰기위해서는 내장 URL 클래스 사용 권장

export class Url {
  constructor (protocol, username, password, hostname, port, pathname, search, hash) {
    this.protocol = protocol;
    this.username = username;
    // ... 귀찮아요
    this.hash = hash;

    this.validate();
  }

  validate() {
    if (!this.protocol || !this.hostname) {
      throw new Error("Must specify at least a " + "protocol and hostname");
    }
  }

  toString () { 
    let url = '';
    url += `${this.protocol}://`
    if (this.username && this.password) {
      url += `${this.username}:${this.password}`
    }

    url += this.hostname;

    if (this.port) {
      url += this.port;
    }
    if (this.pathname) {
      url += this.pathname;
    }

    if (this.search) {
      url += this.search;
    }

    if (this.hash) url += this.hash;

    return url;
  }
}

모든 구성요소를 가져오려면 URL 클래스의 생성자가 필연적으로 커짐

또한 매개변수의 값이 어떤 값인지 인지하고 있어야 함

return new Url('https' , null, null, 'example.copm', null, null, null, null);

여기에 빌더 패턴을 적용하기에 최적의 상황

 

Setter 함수가 있는 Url Builer 클래스를 만들어봅시다.

// urlBuilder.js
export class urlBuilder {
  setProtocol (protocol) {
    this.protocol = protocol;

    return this;
  }

  setAuthentication (username, password) {
    this.username = username;
    this.password = password;

    return this;
  }

  setHostname (hostname) {
    this.hostname = hostname;

    return this;
  }

  setPort (port) {
    this.port = port;

    return this;
  }

  setPathname (pathname) {
    this.pathname = pathname;

    return this;
  }

  setSearch (search) {
    this.search = search;
    return this;
  }
  setHash (hash) {
    this.hash = hash;
    return this;
  }

  build () {
    return new Url(this.protocol, this.username, this.password,
      this.hostname, this.port , this.pathname, this.search, this.hash)
  }
}

어때요? 매우 직관적이고 참 쉽죠?

Url 에 인증정보를 지정하려는 경우 [사용자 이름, 비밀번호]가 모두 필요함을 명확히 함

 

실제 사용을 해봅시다.

// index.js
import { UrlBuilder } from './';

const url = new UrlBuilder()
  .setProtocol('https')
  .setAuthentication('user', 'pass')
  .setHostname('example.com')
  .build();

console.log(url.toString());

가독성 개점

각 setter 함수는 설정하는 매개 변수에 대한 힌트 제공, 클린코드에 도움이 될듯

 

 

Summary

빌더 패턴 : 복잡한 객체를 생성하거나 복잡한 함수를 호출하는 문제의 해결책


Ex) http 내장된 requests() API, HTTP 클라이언트 요청 생성

superagent
  .post('https://example.com/api/person')
  .send({name : 'John Doe' ,role : 'user'})
  .set('accept', 'json')
  .then((response) => {
    // 처리
  })

new 연산자 없음
요청을 실행시키는것은 then() 함수
superagent 객체가 프라미스가 아니라 then() 함수가 빌더객체를 통해 만들어진 요청을 실행하게 하는 thenable 이라는 것

댓글