추상팩토리 패턴 (Abstract Factory Pattern) [세번째]

자, 이제 그럼 마지막으로 이번에는 Abstract Factory 패턴을 이용하여 문제를 해결해보도록 하죠.

  • 객체 생성 전담 클래스를 이용하는 방법
  • Abstract Factory 패턴을 이용하는 방법

"객체 생성 전담 클래스를 이용하는 방법"에서 두 가지 단점에 대해 언급했는데, 첫번째가 객체 생성 시마다 반복해서 일일이 조건 검사가 이루어져야 했다는 점입니다.

객체 생성할 때마다 조건검사를 할 필요가 없게 만들기 위해서는, 이전에 살펴보았던 코드에서 database_factory 를 linux_database_factory 와 aix_database_factory class 가 상속하도록 하고 외부에서는 database_factory 만 보도록 만드는 것입니다.

즉, 아래와 같은 상속을 이용한 class 정의를 합니다. 이전에는 linux platform 이면 linux_session_contol 객체를 생성했고, aix 이면 aix_session_control 객체를 생성하는 조건 검사가 제품(모듈)을 만들 때마다 매번 이루어졌지만, 이번에는 linux 용과 aix 용의 제품군을 만들어내는 factory class 를 중간에 하나 더 끼워넣은 것입니다.

// 사용자에게 open 되는 class - Abstract Base Class
class database_factory
{
public:
    virtual session_control * make_session_control() = 0;
    virtual query_proc * make_query_proc() = 0;
};

// platform 에 따른 제품군을 생성하는 Factory Class
class linux_database_factory : public database_factory
{
    session_control * make_session_control() { new linux_session_control; }
    query_proc * make_query_proc() { new linux_query_proc; }
}

class aix_database_factory : public database_factory
{
    session_control * make_session_control() { new aix_session_control; }
    query_proc * make_query_proc() { new aix_query_proc; }
}

위의 방식을 이용하면 전체적으로 코드가 어떻게 편리하게 변경되는지 살펴보죠.

#include <stdio.h>
#include <stdlib.h>

// linux 의 모듈과 aix 의 모듈의 동일한 인터페이스를 가지도록 
class session_control
{
public:
    virtual ~session_control() = 0;
};

class query_proc
{
public:
    virtual ~query_proc() = 0;
};

session_control::~session_control() {}
query_proc::~query_proc() {}

// platform 별 구체 class - 모듈 별 인터페이스를 상속
class linux_session_control : public session_control 
{
public:
    linux_session_control();
};

class linux_query_proc : public query_proc 
{
public:
    linux_query_proc();
};

class aix_session_control : public session_control 
{
public:
    aix_session_control();
};

class aix_query_proc : public query_proc 
{
public:
    aix_query_proc();
};

// 사용자에게 open 되는 class
class database_factory
{
public:
    virtual session_control * make_session_control() = 0;
    virtual query_proc * make_query_proc() = 0;
};

// 동일한 제품군 별로 객체를 생성해주는 class
class linux_database_factory : public database_factory
{
    session_control * make_session_control() { new linux_session_control; }
    query_proc * make_query_proc() { new linux_query_proc; }
};

class aix_database_factory : public database_factory
{
    session_control * make_session_control() { new aix_session_control; }
    query_proc * make_query_proc() { new aix_query_proc; }
};

// 어떤 객체가 생성되었는지를 보기 위해 생성자에서 찍어봄
linux_session_control::linux_session_control()

{
    printf("linux_session_control\n");
}

linux_query_proc::linux_query_proc()
{
    printf("linux_query_proc\n");
}

aix_session_control::aix_session_control()
{
    printf("aix_session_control\n");
}

aix_query_proc::aix_query_proc()
{
    printf("aix_query_proc\n");
}

// Platform 결정
int platform;


// 사용자 구현 영역
int main(int argc, char * argv[])
{
    database_factory  * fact;
    platform = atoi(argv[1]);  // 편의상. 실제로는 이렇게 사용자 영역에 들어가면 안됨.

    if( platform == 1 ) fact = new linux_database_factory;
    else if( platform == 2 ) fact = new aix_database_factory;
    else
    {
        printf("invalid platform\n");
        return 1;
    }

    fact->make_session_control();
    fact->make_query_proc();
}

실행해보면 이전 코드를 실행했을 때와 결과는 같습니다.
위의 코드를 잘 살펴보면 이전의 코드와 다른 두가지 점이 있습니다. (용어를 잘 생각하면서 이해하시기 바랍니다. ^^)

  • 첫째는, session_control 및 query_proc 모듈(제품)에 대한 객체를 생성(make_xx)할 때마다 platform 조건 검사를 할 필요가 없도록 아예 platform 별 제품군 생성을 위한 class(linux_database_factory, aix_database_factory)를 별도로 두었다는 점입니다. 이러한 class 를 최상위의 추상화 Factory Class(Abstract Base Factory Class)에 대응하여 구체화되었다는 의미로 Concrete Factory Class 라고 부르기도 합니다.
  • 둘째는, 첫째와 같이 구성한 결과로 조건 검사 부분은 초기에 딱 한번만 이루어지면 된다는 점입니다.
    제품을 만들 때마다 검사가 필요한 것이 아니고 초기에 platform 이 결정되면, 해당 platform 에 맞는 제품군 생성 용 class(Concrete Factory Class)에 대한 객체를 생성하여 이를 사용자 Open 용 껍데기 class(Abstract Base Class)-코드 상에서는 database_factory-를 통해 접근하도록 합니다.

위와 같이 이용하는 것이 Abstract Factory 패턴 방식입니다.

이제 많이 좋아졌나요?
자, 이제 새로운 platform 이 추가되었다고 가정해보면 어떤 작업들이 필요할까요 ?
아마도 다음 3 가지의 변경 및 추가작업을 해야할겁니다. hp platform 에서도 지원해야한다고 가정을 해보죠.

  1. 추가되는 platform 에 대해 모듈 별 class 를 추가해야 합니다. 즉, hp_session_control, hp_query_proc class 를 정의해야 합니다.
  2. HP 용 제품군을 생성할 Concrete Factory Class(hp_database_factory)를 정의해야 합니다.
  3. main 함수에서 patform 이 하나 추가되므로 else if 하나가 추가되어야 합니다.

여기까지가 Abstract Factory 패턴에 대한 내용입니다.
어때요 ? Abstract Factory 패턴을 이용한 최종 결과가 마음에 드시나요 ?
왠지 저는 그리 마음에 들지 않는군요. 2 가지 커다란 단점이 눈에 보입니다.
그 단점들이 무엇인지 살펴보도록 하죠.
이것이 결론적으로 Abstract Factory 패턴의 단점이라고 할 수 있습니다.

  • 첫번째, 새로운 platform 마다 class 가 추가되어야 합니다. 즉, 제품군의 숫자가 많아질수록 Concrete Factory Class(제품군 생성 Class) 의 수도 늘어나서 잘못하면 Class 의 수가 지나치게 많아집니다.
  • 두번째, 만약 새로운 제품이 추가된다면 모든 Factory Class 들을 수정해야 합니다. 즉, 위의 예에서 session_control 과 query_proc 외에 storage_manager 모듈 하나가 추가되면 linux_storage_manager/aix_storage_manager class 추가 뿐 아니라, database_factory/linux_database_factory/aix_databas_factory class 들에 make_storage_manager 함수들이 모두 추가되어야 합니다. 한마디로 새로운 제품이 추가되면 쥐약인 구조이죠.

위의 내용을 보면 절대로 Abstract Factory 패턴 하나만으로는 완벽한 모듈화를 설계할 수 없겠죠.
그래서, 디자인 패턴은 어느 하나의 패턴만으로 이상적인 설계를 구현하기는 힘든 경우가 많습니다.
위의 패턴에 다른 패턴 또는 다른 아이디어가 같이 융합이 되어야 좋은 구조가 나올 수 있다는 것을 명심해야 합니다.

여기까지 하구요.
다음 시간 부터는 Builder 패턴에 대해 알아보도록 하겠습니다.

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