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

이번 시간에는 이전 시간에 살펴보았던 코드의 문제점을 개선하는 두가지 방식 중 첫번째 방법에 대해 살펴보겠습니다.

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

이전 시간에 살펴보았던 코드의 가장 큰 문제점은 변경될 가능성이 많은 로직들이 프로그램의 곳곳에 산재해 있다는 점입니다. 즉, 무언가 하나 요구조건이 추가되거나 변경되면 프로그램 이곳저곳을 들쑤시고 다녀야 합니다.
이런 경우에는 십중팔구는 기존에 없던 버그를 양산하게 마련입니다.
이러한 단점을 없애는 방법은 당연히 변경이 될 가능성이 높은 프로그램 영역들을 한쪽으로 몰아버리는 것이겠지요.
이것을 변경의 국지화(Localization of Change)라고 합니다.

그렇다면, 어떻게 그런 부분들을 국지화시킬까요 ?
이 때 Class 의 "정보 은닉" 기능을 사용하면 됩니다.
즉, 변경될 가능성이 많은 정보와 그렇지 않은 정보를 구분해서 변경될 가능성이 많은 정보는 Class 로 내부로 숨겨서 필요할 경우 해당 내부만 변경시켜주면 되는 방식입니다.

코드를 한번 보도록 하죠. 이제 실제 실행시킬 수 있는 코드로 만들어볼까요 ?
개발자들은 코드를 실행해보고 눈으로 결과를 봐야 좀 이해가 빠른 경향이 있죠. ^^
코드가 너무 길어지기 때문에 앞에서 살펴보았던 3 가지 database 의 모듈 중에서 한가지(Storage Manager)는 제외하고 2 가지만 이용하여 구현해 보겠습니다.

#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() {}

// 사용자에게 open 되는 class
class database_factory
{
public:
    session_control * make_session_control();
    query_proc * make_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();
};

// 어떤 객체가 생성되었는지를 보기 위해 생성자에서 찍어봄
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;

session_control * database_factory::make_session_control()

{
    // platform 별로 각각 적합한 객체 생성
    if( platform == 1 )
    {
        return new linux_session_control;
    }
    else if( platform == 2 )
    {
        return new aix_session_control;
    }
    else
    {
        // invalid platform
    }
}

query_proc * database_factory::make_query_proc()

{
    // platform 별로 각각 적합한 객체 생성
    if( platform == 1 )
    {
        return new linux_query_proc;
    }
    else if( platform == 2 )
    {
        return new aix_query_proc;
    }
    else
   
{
        // invalid platform
    }
}

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

    fact.make_session_control();
    fact.make_query_proc();
}

위의 코드에서 platform 을 결정하는 것은 그냥 편의 상 command line argument 를 사용했습니다.
실제로는 platform 을 결정하는 코드가 위와 같이 되지는 않을 것이고, struct utsname 등을 사용해야할 것입니다.
위의 코드를 보면 이전 시간에 살펴본 것과는 달리 사용자가 database_factory 라는 class 만 바라보도록 설계되어 있습니다.
만약 platform 이 추가되더라도 사용자의 코드는 변경될 것이 없습니다.
단지 class 를 제공하는 측에서 신규 platform 에 대한 class 를 별도로 만들고, database_factory 의 멤버 함수들의 내부를 수정하면 될 뿐이죠.

한번 실행해보죠.

$ g++ db.cpp
$ ./a.out 1
linux_session_control
linux_query_proc

$./a.out 2
aix_session_control
aix_query_proc

와우... platform 을 1 로 주면 linux 에서 필요한 모듈객체들이 생성되고, 2 로 주면 aix 에서 필요한 모듈객체들이 자동으로 생성되네요.

어때요?  마음에 드시나요 ?

저는 위의 코드가 썩 마음에 들지는 않습니다.
그 이유는 다음의 두 가지 이유때문입니다.

  1. 여전히 객체를 생성하는 곳마다 비교문장들이 여기 저기 산재해 있어서 새로운 조건을 추가하기 위해서는 기존 소스를 아주 세세히 분석해보아야 한다.
  2. 새로운 조건을 추가할 때 기존의 소스코드와의 dependancy 가 심하다. 즉, 기존 소스코드와 무관하게 독립적으로 추가할 수가 없다.

프로그램의 모듈화라는 관점에서 위의 코드가 아직 부족하다는 의미입니다.

그렇다면 위의 단점은 어떻게 보완할 것인가?
그것이 바로 Abstract Factory 패턴을 이용하는 방법입니다. 이 방법은 다음 시간에 살펴보겠습니다.
 

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