본문으로 바로가기

생성 패턴 - ④ 빌더 패턴

객체를 생성하는 일은 다소 번거롭고 많은 주의가 요구됩니다. 만약 생성 절차가 특별한 규칙을 따라야 한다면 객체를 생성하는 일은 더욱 까다로워집니다. 디자인 패턴에서 생성 패턴은 객체 생성의 어려움을 해소하기 위해 등장했습니다. 생성 패턴은 객체를 만드는 복잡한 과정을 추상화한 패턴입니다. 생성 패턴의 종류는 다음과 같습니다.

  • Factory Pattern
  • Factory Method Pattern
  • Abstract Factory Pattern
  • Builder Pattern
  • Singleton Pattern
  • Prototype Pattern

이번 글에서는 네 번째 빌더 패턴에 대해서 다뤄보도록 하겠습니다.

Builder Pattern

빌더 패턴은 객체의 생성과정이 복잡할 때 이를 간단하게 만들어 줍니다. 예를 들어 하나의 객체를 만들기 위한 인자가 매우 많은 경우를 생각해볼 수 있습니다. 이런 경우 빌더 패턴을 활용해서 보다 쉽게 객체를 생성할 수 있습니다. 예제를 통해서 살펴보도록 하겠습니다.

빌더 패턴의 활용

빌더 패턴은 생성하려는 객체가 매우 복잡할 때 유용합니다. 아래의 고양이 클래스는 매개변수를 세개만 받지만 더 많은 인자를 받는 아주 복잡한 객체라고 상상해 봅시다. 또한 고양이 클래스에 Print 함수가 있다면 SOLID 원칙 중 단일 책임원칙을 위배하지만, 예제의 편의상 Print함수를 포함시켰습니다.

class Cat {
public:
    Cat(int height, int weight, string color)
        :mHeight(height), mWeight(weight), mColor(move(color)){}
    void Print() { cout << mHeight << "cm " << mWeight << "kg " << mColor << endl; }
private:
    int mHeight;
    int mWeight;
    string mColor;
};

이제 이렇게 복잡한(매개변수가 3개가 아니라 매우 많다고 상상해봅시다) 객체를 빌더 패턴의 도움을 받아서 만들어봅니다.

class CatBuilder {
public:
    CatBuilder() {}
    SetHeight(int height) { mHeight = height; }
    void SetWeight(int weight) { mWeight = weight; }
    void SetColor(string color) { mColor = move(color); }
    shared_ptr<Cat> Build() { return make_shared<Cat>(mHeight, mWeight, mColor); }
private:
    int mHeight;
    int mWeight;
    string mColor;
};
int main()
{
    CatBuilder cat_builder;
    cat_builder.SetHeight(10);
    cat_builder.SetWeight(3);
    cat_builder.SetColor("Black");

    auto kitty = cat_builder.Build();
    kitty->Print();

    return 0;
}

이렇게 간단한 Builder 패턴에 대해서 알아보았습니다. 하지만, 빌더 패턴의 종류는 굉장히 다양합니다. 여기서는 그중 몇 가지만 더 다뤄보도록 하겠습니다.

Fluent Interface (흐름식 인터페이스 빌더)

빌더의 Set함수에 아래와 같이 빌더 자신을 반환하도록 설계를 할 수 있습니다.

class CatBuilder {
public:
    CatBuilder() {}
    CatBuilder& SetHeight(int height) { mHeight = height; return *this; }
    CatBuilder& SetWeight(int weight) { mWeight = weight; return *this; }
    CatBuilder& SetColor(string color) { mColor = move(color); return *this; }
    shared_ptr<Cat> Build() { return make_shared<Cat>(mHeight, mWeight, mColor); }
// 멤버변수 생략
};

그러면 클라이언트는 다음과 같이 '.' 연산자를 통해 흐름식 인터페이스(fluent interface)로 빌더를 활용할 수 있습니다.

int main()
{
    CatBuilder cat_builder;
    auto kitty = cat_builder.SetHeight(10).SetWeight(3).SetColor("Black").Build();
    kitty->Print();

    return 0;
}

Concreate Builder (특정 객체를 생성하는 빌더)

앞서 만든 CatBuilder를 상속받아 특정 고양이만을 생성하는 Concreate Builder를 만들 수 있습니다.

아래와 같이 CatBuilder를 상속받은 WhiteCatBuilder와 BlackCatBuilder는 각각 흰색 고양이, 검은색 고양이만 생성할 수 있습니다. 이렇게 특정 속성을 기본값으로 지정하는 빌더를 Concreate Builder라고 합니다.

class WhiteCatBuilder : public CatBuilder {
public:
    WhiteCatBuilder() : CatBuilder() { mColor = "White"; }
};
class BlackCatBuilder : public CatBuilder {
public:
    BlackCatBuilder() : CatBuilder() { mColor = "Black"; }
};
int main()
{
    WhiteCatBuilder white_cat_builder;
    auto kitty = white_cat_builder.SetHeight(10).SetWeight(3).Build();
    kitty->Print();

    BlackCatBuilder black_cat_builder;
    auto nabi = black_cat_builder.SetHeight(15).SetWeight(5).Build();
    nabi->Print();

    return 0;
}

특정 포맷으로 세팅하는 Director

Director는 고양이의 몇 가지 속성을 묶어 하나의 특징을 갖도록 설정하는 객체입니다. 빌더에서 고양이의 속성을 하나씩 설정할 수도 있지만, Director를 사용하여 관련된 여러 속성을 한 번에 설정할 수 있습니다. Director는 다음과 같이 사용할 수 있습니다.

class Director {
public:
    void SetSmallCat(CatBuilder& cat_builder)
    {
        cat_builder.SetHeight(5).SetWeight(3);
    }
    void SetBigCat(CatBuilder& cat_builder)
    {
        cat_builder.SetHeight(30).SetWeight(15);
    }
};
int main()
{
    Director director;
    WhiteCatBuilder white_cat_builder;
    director.SetSmallCat(white_cat_builder);
    auto kitty = white_cat_builder.Build();
    kitty->Print();

    BlackCatBuilder black_cat_builder;
    director.SetBigCat(black_cat_builder);
    auto nabi = black_cat_builder.Build();
    nabi->Print();

    return 0;
}

정리

빌더 패턴의 목적은 여러 복잡한 요소들의 조합이 필요한 객체를 생성할 때 이를 효율적 해결하기 위해 고안되었습니다. 다시 말하자면 혼동될 여지가 없는 쉬운 생성자들과 몇 가지 정도의 파라미터만으로 생성할 수 있는 객체라면 굳이 빌더를 사용할 필요가 없습니다. 또한 빌더 패턴의 종류는 굉장히 다양하지만, 최소한의 기능으로 구현이 가능하다면 여기서 다룬 간단한 빌더를 활용하는 것이 좋은 방법이 될 수 있습니다. 다음 글에서는 싱글톤 패턴에 대해서 다뤄보도록 하겠습니다.


<출처>

유튜브 채널 "코드없는 프로그래밍": https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg

길벗 출판사: 모던 C++ 디자인 패턴(Dmitri Nesteruk 저)