Template을 이용한 Observer 패턴 – 2단계

이제 여기서부터는 개선의 여지를 찾아보겠습니다. 어떻게 하면 EventSrc 클래스가 담당하고 있는 Fire 함수를 Observed에게 전가시킬 수 있을까요? 그래서 EventSrc 클래스를 없앨 수 있겠는가? 그 해답은 함수자(Functor)에 있습니다.

먼저 연습겸해서 간단하게 함수자를 사용해서 1단계의 코드를 수정해 보도록 하겠습니다. 아래는 새롭게 추가한 함수자에 대한 클래스입니다.

class Functor {
private:
    int arg_;

public:
    Functor(int arg) {
        arg_ = arg;
    }

    void operator()(Observable *pOb) const {
        pOb->OnEvent(arg_);
    }
};

이 함수자를 사용하면, EventSrc 클래스의 Fire 함수가 아래처럼 무척 깔끔하게 작성됩니다.

void Fire(int a)
{
    std::for_each(m_listObserver.begin(), m_listObserver.end(), Functor(a));
}

하지만 이런 방식은 Fire 함수를 깔끔하게 만들어준다는 사소한 장점은 있지만, 다른 어플리케이션에 적용할때 매번 함수자를 각각의 경우에 맞게 새롭게 코딩해줘야 하는 수고로움이 더욱 많습니다. 또한 여전이 Observed에서는 자신이 관리하고 있는 Observer 객체들이 호출해야할 함수가 무엇인지를 알 길이 없습니다.

하지만 여기서 곰곰이 생각해 보면 이 함수자를 Observed 클래스의 inner class로 정의해보는 것에 대한 아이디어가 떠오릅니다. 게다가 이 함수자 역시 template로 정의해서 Observed 클래스가 관리하고 있는 Observer 객체들이 호출해야할 함수를 타입으로 받아 버린다면 Observed가 알아야할 Observer의 정보를 모두 Observed에게 넘겨줄 수 있게되어, Fire의 책임을 Observed가 맡을 수 있게 됩니다. 그러면 더 이상 EventSrc는 필요치 않게 되고요. 이러한 아이디어에 착안해서 새롭게 구현된 Observed 클래스는 다음과 같습니다.

template 
class Observed
{
public:
    class Firer1 {
    public:
        explicit Firer1(T_result (T::*pMember)(T_arg1), T_arg1 arg1) {
            m_pMemFunc = pMember;
            m_arg1 = arg1;
        }

        T_result operator()(T* pClass) const {
            return ((pClass->*m_pMemFunc)(m_arg1));
        }

    private:
        T_result (T::*m_pMemFunc)(T_arg1);
        T_arg1 m_arg1;
    };

public: // type define
    typedef std::list typeObservers;

protected: // private attribute
    typeObservers m_listObserver;
    T_result (T::*m_pMemFunc)(T_arg1);
}

public: // ctr & dtr
    Observed(T_result (T::*pMember)(T_arg1)) {
        m_pMemFunc = pMember;
    }
    virtual ~Observed() {}

public: // operator
    void RegisterObserver(T *pOb)
    {
        m_listObserver.push_back( pOb );
    }

    void UnRegisterObserver(T *pOb)
    {
        m_listObserver.remove(pOb);
    }

public: // Fire!
    void Fire(T_arg1 arg1)
    {
        std::for_each(m_listObserver.begin(), m_listObserver.end(), 
            Firer1(m_pMemFunc, arg1));
    }
};

굵은 청색 폰트로 된 것이 수정되거나 새롭게 추가된 것입니다. Observed가 관리하고 있는 Observer의 호출함수의 반환값과 인자, 그리고 호출함수에 대한 타입을 template 인자인 T_result와 T_arg1으로 받고, 호출함수는 생성자에서 받아 맴버변수인 m_pMemFunc에 저장하도록 하였습니다.

이렇게 새롭게 구현된 Observed를 이용해서 Client 측에서 사용하는 코드는 다음과 같습니다.

int _tmain(int argc, _TCHAR* argv[])
{
    Observed *pES = 
        new Observed(&Observable::OnEvent);

    Observable_A *pOA = new Observable_A();
    Observable_B *pOB = new Observable_B();

    pES->RegisterObserver(pOA);
    pES->RegisterObserver(pOB);

    pES->Fire(99);

    delete pOA;
    delete pOB;
    delete pES;

    return 0;
}

이제 마지막으로 하나 더 짚고 정리를 하겠습니다. 여기서 한가지 큰 문제가 있는데 그것은 Observed가 관리하고 있는 Observer의 호출해야할 함수의 인자 개수에 관한 문제입니다. 지금가지의 경우는 단지 하나의 인자만을 받는 경우지만 두개 이상의 인자를 받는 경우에 대한 처리도 필요하지요. 하지만 이것 역시 그리 어렵지 않게 해결할 수 있습니다. Observed의 inner class인 함수자의 이름이 Firer1인 이유는 하나의 인자를 받는 함수자이기 때문에 Firer 뒤에 1을 붙인 것입니다. 그렇다면 이제 2개의 인자를 받는 Firer2 함수자를 정의하고 인자를 2개를 받는 Fire 맴버함수를 하나더 만들어 두면 됩니다.

이제 Client는 1단계처럼 관리하고자 하는 Observer에 대해 EventSrc와 Observable 클래스 모두를 정의할 필요가 없이, Observable 클래스 단하나만 신경 쓰면 되게 되었습니다. 그리고 Observer에 대한 모든 관리에 대한 책임을 오직 Observed가 맡게 되어 SRP를 지키게 되었습니다.

Template을 이용한 Observer 패턴 – 1단계

C++의 template을 이용해서 Observer를 구현하는 것에 대한 단계적 설명입니다. 원본은 데브피아에서 김태현(tipani)님이 올려 놓으신 글을 기반으로 작성했으며 한단계 더 업그레이드 했습니다. 제가 늘 느껴오는 것이지만 C++의 template은 기존의 클래스간 관계도에 한정해 볼적에 그 디자인을 획기적으로 개선한다는 점에서 그 판도를 확 바꿀 수 있는 강력한 개념이라고 생각합니다. 아무쪼록 제가 김태현님의 글을 보고 매우 재미있게 template에 한발짝 다가섯듯이 여러분도 제 글을 통해 template에 한발짝 다가 설수있다면 정말 기쁘겠습니다. 참고로 이 글을 읽기 전에 Observer 패턴이 무엇인지 패턴입문서를 살펴보시길 바랍니다. 또한 이 글의 진행은 단계 단계 개선해 나가는 흐름으로 진행된다는 점에 유의하시길 바랍니다.

먼저 1단계입니다. 아래의 코드는 Observer들의 관리에 대한 책임을 맡고 있는 Observed 클래스입니다.

template 
class Observed {
public:
    Observed() {}
    typedef std::list typeObservers;
    virtual ~Observed() {}

    void RegisterObserver(T *pOb) {
        m_listObserver.push_back( pOb );
    }

    void UnRegisterObserver(T *pOb) {
        m_listObserver.remove(pOb);
    }

protected:
    typeObservers m_listObserver;

};

Observed가 관리하는 Observer 클래스인 Observabe 입니다. 단순히 Observed가 호출할 Observer의 OnEvent 함수가 순수가상함수로 선언되어 있습니다.

class Observable {
    virtual void OnEvent(int a) = 0;
};

그리고 아래는 Obserable를 상속받아 구현한 클래스들입니다.

class Observable_A : public Observable
{
    virtual void OnEvent(int a) {
        std::cout << "Fire_A -> " << a << std::endl;
    }
};

class Observable_B : public Observable
{
    virtual void OnEvent(int a) {
        std::cout << "Fire_B -> " << a << std::endl;
    }
};

이제 마지막으로 Observed가 자신이 관리하고 있는 Observable의 OnEvent를 호출해 줘야 하는데, Observed 클래스는 자신이 관리하고 있는 Observable의 타입을 모르기 때문에 Observed와 Observable의 관계를 연결해 주기 위한, Observed로부터 상속받은 EventSrc 클래스가 필요합니다.

class EventSrc : public Observed
{
    void Fire(int a)
    {
        typeObservers::itrator it;
        for(it=m_listObserver.begint(); it!=m_listObserver.end; ++it) {
            (*it)->OnEvent(a);
        }
    }
};

드디어 1단계의 Observer 패턴의 구현이 완성되었습니다. 실제 사용하는 예는 다음과 같습니다.

int main() {
    EventSrc *pES = new EventSrc();
    Observable *pO_A = new Observale_A();
    Observable *pO_B = new Observale_B();

    pES->RegisterObserver(pOA);
    pES->RegisterObserver(pOB);

    pES->Fire(99);


    delete pO_B;
    delete pO_A;
    delete pES;

    return 0;
}

1단계에서 산출된 소스만으로도 충분할 수도 있겠지만, 큰 문제점이 하나 있습니다. 그것은 바로 SRP(Single Responsiblity Principle)를 위반한다는 사실입니다. 즉, Obserable를 관리하는 책임을 Observed와 EventSrc라는 두개의 클래스가 책임을 나눠서 지고 있다는 점입니다. 그럴수밖에 없는 이유는 본문에 언급을 했구요.

이제 다음 2단계 이후부터는 이러한 SRP의 원칙을 지켜나가는 것을 해결문제로 다뤄나가면서 점차적으로 개선된 Observer 패턴을 구현해보겠습니다.

[여행] 제부도

날짜는 10월 14일. 대학교 시절에 같은 학교울타리에 있다가, 몇년전에 나는 서울로, 하나는 대전으로.. 또 하나는 경기도 화성으로 각자의 길을 찾아 떠났던 친구들.. 지난 추석때 모두 함께 만나지 못하다가, 어쩐일로 모이게 되어 떠난 제부도 여행. 제부도 조개가 유명하다고 해서 갔다..








조개를 케는 사람들도 제법 있었고, 연인.. 가족들과 나들이 나오신 분들도 많았고.. 주말이라 많은 사람들로 붐볐다. 서로 자기집에서 조개구이를 먹으라고 문앞에서 호객행위를 하는 환하게 웃으며 우스꽝스러운 행동을 하던 아저씨들. 사람들이 그냥 지나가면 한마디 뱃는데.. ㅅㅂㄹㅁ라고.. 똑똑히 봤어요.. ㅡ_ㅡ ㅅㅂㄹㅁ!

조개는 실망. 니가 정녕 제부도 조개냐.. 그냥 저기 앞바다 조개냐.. 묻고 싶었다. 서울 신림에서 먹었던 조개구이보다 별로였다. 너도 중국산이냐? 조개구이 맛에 대한 아쉬움을 뒤로 하고 돌아오는 길의 순대국밥은 정말 맛있었다. 오던 길에 도로에서 산 포도도 맛있었고…

처음 출발때 약속 시간을 지키지 못하고 늦어, 친구들에게 너무 미안했었다.. 찍고 싶었던 갈매기는 못찍었지만, 집에서 몇년 묶은 디카를 꺼내들었고, 오랜만에 쏘인 바다바람과 날씨까지 좋았고.. 친구들과 오랜만에 모여 기분 좋은 여행이 되었다.

FreeType을 이용한 Font 처리

FreeType의 주된 용도는 Anti-Aliasing 처리가 된 세련된 폰트의 출력에 있습니다. FreeType은 Windows는 물론이고, Linux나 MacOS에서 특히 더 많이 사용되고 있는 오픈소스 라이브러리입니다. 3D에서 한글출력을 위해 FreeType을 사용하는데, 류광님의 주도하에 개발된 glan이라는 한글출력 라이브러리도 안티알리아싱 처리된 문자의 출력을 위해서 FreeType을 활용하고 있는 것으로 알고 있습니다. OpenGL에서 한글출력을 고민하고 계시다면 반드시 glan에 관심을 가져보시길 바랍니다.

저 역시 FreeType에 관심을 가지게 된 이유도 3D에서의 한글출력 문제 때문이였습니다. 하지만 기존의 한글출력 방식은 하나의 이미지에 한글을 그려 놓고, 출력하고자 하는 문자을 텍스쳐 맵핑 방식으로 출력하는 방법을 사용합니다. 장점은 속도가 무척 빠르기 때문에 게임에서 주로 사용됩니다. 하지만 단점은 큰 크기의 한글을 출력하고자 할때 텍스쳐 이미지가 그에 따라 확대되어 커지게 되므로 뭉개지는 결과가 발생하게 되고, 다양한 한글 폰트를 사용하기 위해서 그 다양한 한글 폰트의 종류만큼 한글 맵핑 이미지를 미리 생성해 놓아야 합니다.


그러나 어찌되었든지 게임이라는 장르에서는 한글을 크게 출력할 필요가 없고 다양한 폰트를 사용할 필요가 없기때문에 이러한 단점이 문제가 되지 않습니다. 그러나 게임이 아닌 경우에 다양한 한글 포트와 한글의 크기에 상관없이 미려한 한글 출력을 원할 경우에 다른 방법은 없을지 고민이였습니다.

쉽게 생각할 수 있는 것이 한글을 구성하는 좌표를 구해서 Mesh 형태로 뿌리는 것이였습니다. 물론 속도면에서의 희생이 따르는 방법이긴 합니다만, 경험상 화면상에 문제 출력은 사실 수십자 정도에 불과합니다. 즉, 많아봐야 수백자라는 이야기이지요. 하지만 여려운 점은 글자를 구성하는 좌표를 얻어오는 방법이였습니다. 방법은 2가지인데, 하나는 Windows API를 이용하는 방법과 앞서 언급한 FreeType을 이용하는 방법이 있습니다.

저는 FreeType을 사용하기로 결정하였는데, OpenGL과 같은 플렛폼 독립적인 라이브러리와 궁합이 정확히 맞아 떨어진다는 생각에서 였습니다. 제가 시험삼에 작성한 코드의 그 결과 화면은 위와 같습니다.

지정된 폰트의 글자에 대한 Anti-Aliasing 처리된 한글 표시와 그 글자의 구성 좌표를 표시하고 있습니다. 구성좌표는 박스로 나타내었는데, 빨강색 박스는 곡선처리를 위한 제어점(Control Point)입니다.

FreeType의 초기화, 안티알리아싱 처리된 글자의 출력, 글자의 구성 좌표를 얻는 방법에 대한 전체 코드가 담긴 첨부파일을 참고하시길 바랍니다. 2006년이라는 시대에 한글처리 문제로 고민해야 하는 웃지 못할 상황에 도움이 되길 바라는 마음에서 글을 올립니다.