빌더 패턴 (Builder Pattern) [첫번째]

1. 객체 생성을 위한 디자인 패턴
  • Singleton(싱글톤) 패턴
  • Abstract Factory 패턴
  • Builder 패턴
  • Factory Method 패턴
  • Prototype 패턴

이번 시간부터는 객체 생성을 위한 디자인 패턴 중 Builder 패턴에 대해 알아보겠습니다. 일단 Builder 패턴을 정의해보면 "Builder 패턴은 객체를 생성하되, 그 객체를 구성하는 부분부분을 먼저 생성하고, 이를 조합함으로써 전체 객체를 생성하며, 생성할 객체의 종류가 손쉽게 추가, 확장이 가능하도록 고안된 패턴"입니다.

보통 사람들은 객체가 생성되면 생성자에 의해 한방에 생성이 된다고 생각합니다.
왜냐하면, 객체의 실질적인 생성과정은 숨겨지고 눈에 보이는건 그게 전부이니까요.
하지만, 객체가 생성되기 위해서는 내부적으로 객체를 구성하는 데이터 멤버들에 대한 공간할당 등 객체를 구성하는 멤버들을 일일이 생성하는 과정을 거치게 됩니다.
이 특성을 이용한 것이 Builder 패턴이라고 할 수 있습니다.
즉, 생성해야할 객체의 규모가 크지만, 이를 잘게잘게 쪼개서 미리미리 객체를 구성하는 부분을 조금씩 생성해둔 후 최종적으로 이를 조합하여 목표 객체를 생성하는 방법입니다.
이렇게 하면 전체 객체를 생성하는데 소요되는 시간을 절약할 수 있을 뿐 아니라 , 중간 과정에서 사용자가 입력한 값 등을 최종 객체 생성 시점까지 어딘가에 별도록 관리해두어야 하는 비용 등이 사라질 것입니다.

이러한 Builder 패턴의 예가 어떤 것이 있을까요 ?
가장 와닿는 것이 아마도 통역소프트웨어일 것 같습니다.
이 소프트웨어는 우리가 다수의 문장을 한국어로 입력한 후 원하는 언어를 선택하면 해당 언어로 즉시 번환하여 말을 해주는 소프트웨어입니다.
이 소프트웨어 이름을 지금부터 QuickTrans 라고 불러봅시다.

예를 들어, 우리가 QuickTrans 에게 아래의 통역 요청 문장들을 입력했다고 해보죠.
"나는 오늘 참 즐겁다. 김치를 담갔는데 그 맛이 아주 좋았다. 내년까지 이 김치를 다 먹을 수 있겠지?"

위의 요청은 단 3 개의 문장으로 이루어져 있습니다만, 만약 한번에 통역해야할 문장이 아주아주 긴 경우 이를 한번에 처리하는 것 보다는 한문장 한문장을 변역할 때마다 그 결과를 차근차근 결과물에 합쳐나가는 방식이 좋을 것입니다.

QuickTrans 를 구현해보는데 일단 함수 방식으로 접근해보죠. 아래의 pseudo code 를 봅시다.
이 코드는 "디자인 패턴 이렇게 활용한다 - c++로 배우는 패턴의 이해와 활용"이라는 책의 example 을 좀더 간단하게 수정한 것입니다.

#define UNDEF    0
#define KOREAN  1
#define ENGLISH 2
#define FRENCH  3

// 한문장을 저장하는 class
class sentence
{
public:
    sentence() { data = ""; type = UNDEF; }
    void get_type() { return type; }
    void get_string() { return data; }
private:
    string data;
    int      type;  // 평서문, 의문문
};

// 번역된 모든 문장을 저장하는 class
class last_result
{
public:
    string get_result() { return last_string; }
    void add_result(string s) { last_string += s; }
private:
    string last_string;
};

void translate(char *in_file, last_result &out, int wanted_lang)
{
    string  one_result;
    sentence next;

    // 파일에서 한 문장을 읽어서 type 문석 후 sentence 객체에 저장
    while( next = read one sentence from in_file )
    {
        switch(next.get_type())
        {
            // 문장의 type 에 따라 각기 다른 함수 호츨
            case 평서문 : trans_평서문(next.get_string(), wanted_lang); break;
            case 의문문 : trans_의문문(next.get_string(), wanted_lang); break;
            default : printf("invalid sentence\n");
            break;
        }
    }
    out.add_result(one_result);
}

// 평서문의 번역
string trans_평서문(string s, int wanted_lang)
{
    string out;
    switch(wanted_lang)
    {
        case ENGLISH:
            // translate to ENGLISH
            break;
        case FRENCE:
            // translate to FRENCH
            break;
    }
    return out;
}

// 의문문의 번역
string trans_의문문(string s, int wanted_lang)
{
    string out;
    switch(wanted_lang)
    {
        case ENGLISH:
            // translate to ENGLISH
            break;
        case FRENCE:
            // translate to FRECH
            break;
    }
    return out;
}

// QuickTrans 시작
int main()
{
    last_result out;

    translate("input.txt", out, ENGLISH);
    printf("%s\n", out.get_result());
}

위의 예를 보면 QuickTrans 는 다음과 같은 처리 과정을 거칩니다.

  • 사용자가 번역이전 문장들을 입력해둔 input.txt 파일에서 한문장 씩 읽어서 이 문장이 평서문인지 의문문인지 문장 타입을 판별한다.
  • 문장 타입에 따라 알맞은 번역 처리 함수를 호출한다.
  • 번역 처리 함수에서는 목표 언어가 무엇인지에 따라 번역 처리한 결과 문장을 리턴한다.
  • 리턴된 번역 결과 문장은 전체 결과를 저장하는 last_result 객체에 저장된다. 사용자는 최종 결과물 확인을 위해 last_result 객체의 get_result() 함수를 호출해보면 된다.

위의 코드는 언듯 보면 잘 구현된 듯이 보이지만, 상당히 큰 문제점을 가지고 있습니다.
가장 큰 문제점은 요구사항의 추가나 변경 시에 어디를 어떻게 뜯어고쳐야할지 수정범위가 불분명하다는 점입니다.
즉, 재사용할 모듈의 경계가 모호하여 자칫 프로그램 전체를 엄청나게 수정해야하는 부담이 생길 수도 있습니다.

만약 중국어 번역 기능이 추가된다면 ?
거의 모든 함수들을 건드려야겠네요. ㅠㅜ

이렇게 구조적(Structure) 개념으로 접근하면 모듈들의 재사용에 문제가 되는 경우가 많습니다. 그래서, 때로는 객체지향적 개념으로 접근하면 유리한 경우가 상당히 많습니다.

You may also like...

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x