팩토리 메소드 패턴 (Factory Method Pattern)

1. 객체 생성을 위한 디자인 패턴

  • Singleton(싱글톤) 패턴
  • Abstract Factory 패턴
  • Builder 패턴
  • Factory Method 패턴
  • Prototype 패턴

이번 시간에 살펴볼 패턴은 객체 생성을 위한 디자인 패턴 중 Factory Method 패턴입니다.
Factory Method 패턴을 한마디로 정의하면 "대행 함수를 통한 객체 생성 방법"입니다.
우리는 자신이 잘 모르는 분야이거나 어려운 일을 처리해야할 때 그 분야의 전문가에게 일을 처리하도록 맡기기도 합니다.
마찬가지로, 객체를 생성하는 작업도 복잡하거나 어려울 경우 그 역할을 담당하는 함수를 별도로 둘 수 있는데 Factory Method 패턴은 이와 관련된 방법입니다.

이제 실제 예를 가지고 설명을 드리겠습니다.
우리가 윈도우 환경에서 특정 문서파일을 더블클릭하면 자동으로 Microsoft Word 와 같은 문서 Application 이 기동되고, 더블 클릭한 문서가 화면에 보여지게 됩니다.

이 때 잘생각해보면 내부적으로는 두가지 객체가 필수적으로 생성될 것입니다. 한가지는 Application 객체이고, 다른 하나는 문서에 대한 객체입니다.

그렇다면, 이 두가지 객체를 생성하는 주체는 누구일까요 ? 더블클릭 시 자동으로 Application 이 기동되도록 하는 것, 즉 Application 객체가 생기도록 하는 것은 명확히 Windows 운영체제의 몫일 것입니다.
그렇다면 열린 문서에 대한 객체의 생성은 누가 하게 될까요 ?
운영체제 ?
아닙니다.

만약 문서에 대한 객체를 운영체제가 생성해야 한다면, 운영체제는 모든 문서 형식들을 알고 있어야 한다는 말인데, 그렇게 되면 새로운 문서 형식이 생길 때마다 운영체제는 변경을 감수해야한다는 말과 같습니다.
아마도, 문서 객체를 생성하는 것은 그 문서 형식을 아주 잘 알고 있는 Application 일 것입니다.
즉, 운영체제는 어떤 문서인지는 전혀 관심없고, new_doc() 이라는 함수만 호출해주면 Application 이 알아서 문서 객체를 생성해주는 형식이어야하지 않을까요 ?

그렇다면, 운영체제가 어떤 문서인지 전혀 관심갖지 않아도 되게 하려면 어떻게 해야 할까요 ?
우리는 지금까지 많은 경우에서 인터페이스를 제공하기 위해 상속을 이용하는 예들을 보아왔습니다.
즉, 운영체제에게는 다양한 문서형식을 신경쓸 필요없도록 동일한 인터페이스를 제공하고, 하위의 Class 들이 실제적으로 문서 객체를 생성하도록 하려면 상속 개념을 이용해야 합니다.

다음의 코드를 보죠.

class application
{
public:
    virtual void new_doc(char * file_name) = 0;
};

class hwp_application : public application
{
public:
    void new_doc(char * file_name)
    {
        hwp_document * doc = new hwp_document;
        doc->open(file_name);
        // do something
    }
};

class word_application : public application
{
public:
    void new_doc(char * file_name)
    {
        word_document * doc = new word_document;
        doc->open(file_name);
        // do something
    }
};

위의 예를 보면 운영체제에게는 application 이라는 인터페이스용 class 가 제공되고, 운영체제는 new_doc 이라는 호출함수만 알고 있으면 됩니다.
그러면, 각 문서 형식에 맞는 application class 들은 알아서 형식에 맞는 문서 객체들을 생성해주고, 기타 여러 작업들을 수행하겠죠.

그런데, 위에서 좀 비효율적이라고 생각되는 부분이 없나요?

위의 코드를 잘 보면 hwp_application 의 new_doc 함수와 word_application 의 new_doc 함수가 문서객체를 생성하는 부분(붉은색)을 제외하고는 동일한 코드로 작성된 것을 알 수 있습니다.
이렇게 되면, new_doc 의 구현 내용이 변할 경우 모든 class 의 함수 구현 부분들을 수정해야하는 부담이 따르겠죠.

이는 당연히 new_doc 의 구현을 각 application class 마다 따로따로 했기 때문일 겁니다.
그렇다면, 이러한 단점을 없애려면 이러한 구현을 어느 한군데로 단일화해야할 것이고, 그 집중되는 가장 적합한 장소가 결국은 상위 class 인 application class 이지 않을까 생각이 됩니다.
모든 하위 application 들을 대표할 수 있는 class 가 application class 이니까요.
즉, create_doc 이라는 함수를 별도로 빼서 application class 의 new_doc 함수에서 해당 함수를 호출해주도록 변경하는게 좋을 것 같습니다.

그런데, 이렇게 구현을 상위 class 로 올리려면 2 가지 문제를 고려해야 합니다.

  • create_doc 함수의 결과로 나오는 실제 문서객체는 hwp 와 word 가 서로 달라야할 것입니다.
  • 그러나, application class 단에서는 create_doc 의 결과로 나오는 문서 객체가 어떤 응용 class 이냐에 상관없이 동일하게 취급되어야할 것입니다.

코드를 좀 볼까요 ?

#include <stdio.h>
class document
{
public:
    virtual int open(char * file_name) = 0;
};

class hwp_document : public document
{
public:
    int open(char * file_name )
    {
        printf("open hwp_document (%s)\n", file_name);
    }
};

class word_document : public document
{
public:
    int open(char * file_name )
    {
        printf("open word_document (%s)\n", file_name);
    }
};

class application
{
public:
    void new_doc(char * file_name)
    {
        // create_doc 을 호출하고 document 라는 대표 문서객체를 받는다.
        // 즉, 이 class 에서는 문서의 형식을 신경쓰면 안된다.
        document * doc = create_doc();
        doc->open(file_name);
    }

    // 실제로 생성되는 문서 객체는 각 응용에 맞는 것을 생성해야 하므로
    // 하위 class 로 구현을 넘긴다.
    virtual document * create_doc() = 0;
};

class hwp_application : public application
{
public:
    document * create_doc()
    {
        return new hwp_document;
    }
};

class word_application : public application
{
public:
    document * create_doc()
    {
        return new word_document;
    }
};

int main()
{
    hwp_application   hwp;
    word_application  word;

    hwp.new_doc((char *)"aa.txt");
    word.new_doc((char *)"bb.txt");
}

여러분도 직접 위의 코드를 작성하여 g++ aa.cpp 와 같이 컴파일한 후 실행해보시기 바랍니다.
결과는 아래와 같습니다.

$ g++ aa.cpp
$ ./a.out
open hwp_document (aa.txt)
open word_document (bb.txt)

위의 클래스 구조를 그림으로 표현해보면 다음과 같습니다.

 

그림을 보면, application class 를 상속하여 concrete app
lication class(hwp_application, word_application)들을 둠으로써 해당 class 들이 실질적인 문서 객체를 생성하도록 하였습니다. 이렇게 실질적인 업무를 create_doc() 이라는 멤버함수를 통해 하위 class 에 대행을 시켰죠.
이러한 방법이 Factory Method 패턴입니다.

하지만, 위의 코드도 한가지 단점이 있어 보이네요.
문서 형식이 추가되게게 되면 concrete application class 정의 또한 늘어날 수 밖에 없어 보입니다.

참고로 이럴 때 사용하기 편리한 개념이 template 입니다.

#include <stdio.h>
class document
{
public:
    virtual int open(char * file_name) = 0;
};

class hwp_document : public document
{
public:
    int open(char * file_name )
    {
        printf("open hwp_document (%s)\n", file_name);
    }
};

class word_document : public document
{
public:
    int open(char * file_name )
    {
        printf("open word_document (%s)\n", file_name);
    }
};

class application
{
public:
    void new_doc(char * file_name)
    {
        // create_doc 을 호출하고 document 라는 대표 문서객체를 받는다.
        // 즉, 이 class 에서는 문서의 형식을 신경쓰면 안된다.
        document * doc = create_doc();
        doc->open(file_name);
    }

    // 실제로 생성되는 문서 객체는 각 응용에 맞는 것을 생성해야 하므로
    // 하위 class 로 구현을 넘긴다.
    virtual document * create_doc() = 0;
};

template <class doc_type>
class concrete_application : public application
{
public:
    document * create_doc()
    {
        return new doc_type;
    }
};

int main()
{
    concrete_application<hwp_document> hwp;
    concrete_application<word_document> word;

    hwp.new_doc((char *)"aa.txt");
    word.new_doc((char *)"bb.txt");
}

위의 코드를 보면 hwp_applicaion 및 word_application 이 모두 없어지고 template class 만 남았네요.
이 하나의 class 로 모든 문서 형식의 객체를 생성할 수 있는 concrete application class 가 됩니다.
Factory Method 패턴에 template 을 적용하면 무척 편리하겠네요. ^^
다른 패턴들도 마찬가지입니다. 실제 설계에서는 정답이라는게 없다는 것을 기억하세요. ^^

오늘은 하루에 하나의 패턴을 끝내느라 내용이 길었네요. Factory Method 패턴은 다음과 같은 상황에서 사용하면 편리합니다.

  • 구체적으로 어떤 class 의 객체를 생성할지 미리 알지 못하는 경우
  • 하위 class 가 객체를 생성하기를 원할 때
  • 객체의 종류 별로 객체 생성 부분을 국지화시키고자 할 때. (하위 class 에게 책임을 각각 분산시킴)

다음 시간에는 복제를 통한 객체 생성방법인 prototype 패턴에 대해 공부해 보겠습니다.

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