Design Pattern

빌더 패턴 Builder Pattern

La.place 2019. 4. 28. 17:22

 추상 팩토리 패턴과 팩토리 메서드 패턴에 이은 세 번째. 빌더 패턴(Builder Pattern)이다. 개인적으로 빌더 패턴은 앞의 두 패턴보다 자주 사용하고 있는데, 오늘은 design-patterns-JS의 예제를 가지고 설명을 해보도록 하겠다.

 빌더 패턴은 무언가를 뭔가를 조합해주는 패턴이라 이해하면 쉽다. 웹에서 많이 사용하는 Request Query를 예를 들어보도록 하자. Request(리퀘스트)는 크게 URL, METHOD(GET, POST, PUT, DELETE), 그리고 데이터 부분으로 이루어져 있다. 이제 우리는 어떤 리퀘스트 정보를 담고 있는 클래스를 만들어 보도록 하자.

class Request {
    constructor(url, method, data) {
        this.url = url;
        this.method = method;
        this.data = data;
    }
}

let getRequest = new Request('https://github.com/hg-pyun', 'GET', null);
let postRequest = new Request('https://github.com/hg-pyun', 'POST', { id: 'hg', password: 1234});

 구조는 단순하다. url, method, 그리고 data를 입력받아서 Request정보를 가진 객체를 생성한다. 그러나 이 단순한 구조는 두 가지 문제점을 가지고 있다.

 첫째, 생성자를 통해 데이터를 받기 때문에, 데이터가 없을 경우 null을 명시적으로 넣어야 한다. 이런 구조의 새로운 인자를 받아야 할 경우 생성자를 수정해 줘야 할 뿐만 아니라, 길이가 길어짐에 따라 코드의 가독성도 좋지 않다.
 둘째, 어떤 위치에 어떤 타입의 데이터가 들어가야 하는지 명시적이지 않다. 따라서 constructor(url, data, method) 같이 사용자가 실수하여 넣을 가능성도 존재하며, 사용자가 매번 정확한 위치에 들어갔는지 체크해야 하는 부담을 갖는다.
 셋째, 유효성 검사를 위해 생성자에만 불필요한 코드가 장황하게 들어갈 가능성이 있다. 물론 생성자 내부에서 함수로 캡슐화할 수 있지만 불필요한 책임을 가질 가능성이 더 크다.
 자 이제 위 문제점을 해결하기 위해서 빌더 패턴을 적용해 보도록 하자. 빌더 패턴은 setter 메서드와 체이닝을 통해서 특정 객체를 조합해가는 패턴이다. 아래 코드는 특정 리퀘스트를 조합하는 RequestBuilder이다.

class Request {
    constructor() {
        this.url = '';
        this.method = '';
        this.data = null;
    }
}

class RequestBuilder {
    constructor() {
        this.request = new Request();
    }

    forUrl(url) {
        this.request.url = url;
        return this;
    }

    useMethod(method) {
        this.request.method = method;
        return this;
    }

    setData(data) {
        this.request.data = data;
        return this;
    }

    build() {
        return this.request;
    }
}

let getRequest = new RequestBuilder()
    .forUrl('https://github.com/hg-pyun')
    .useMethod('GET')
    .build();

let postRequest = new RequestBuilder()
    .forUrl('https://github.com/hg-pyun')
    .useMethod('POST')
    .setData({
        id: 'hg',
        password: 1234
    })
    .build();

 리퀘스트 객체의 생성자에서는 초기화하는 역할만 가지도록 하자. 이제 리퀘스트 객체의 속성들은 각각의 일종의 setter 메서드들을 이용하여 값을 할당해준다. 빌더를 통해 데이터를 주입해줌으로써, 데이터가 없는 경우에도 문제가 되지 않고 순서를 보장해줄 필요도 없다. 또한 새로운 필드가 추가되었을 때, 해당하는 새로운 setter 메서드만 추가해 주기만 하면 되며, 각각의 setter들 안에서 해당하는 필드에 대한 유효성 검사만 해주면 되기 때문에 코드도 훨씬 깔끔해지고 단일 책임 원칙(SRP: Single Responsibility Principle)도 잘 지켜지는 것을 볼 수 있다.

class Request {
    constructor() {
        this.url = '';
        this.method = '';
        this.data = null;
        this.header = [];
    }
}

class RequestBuilder {
    // ... 위와 동일
    addHeader(header) {
        this.request.header.push(header);
          return this;
    }

    build() {
        return this.request;
    }
}

let getRequest = new RequestBuilder()
    .useMethod('GET')
    .addHeader('someHeader1: value')       // 새로운 헤더1 추가
    .addHeader('someHeader2: value')        // 새로운 헤더2 추가
    .forUrl('https://github.com/hg-pyun')  // 위치가 바뀌어도 문제 X
    .build();

 빌더 패턴은 여러가지 옵션에 대한 처리를 꽤나 깔끔하게 처리해 주기 때문에, 사용할만한 부분이 많으므로 기억해 두도록 하자.