Patterns in “PATTERN-ORIENTED SOFTWARE ARCHITECTURE”

Layer A.P. 어플케이션을 구조화하기 위해 서브 태스크(Subtask)들을 그룹으로 묶기 위해 분해한다. 공통된 추상 레벨에 있는 서브 태스크들끼리 묶어서 그룹으로 분류한다.

Pipes and Filters A.P. 데이터 스트림을 처리하는 시스템 구조를 제공한다. 각 프로세싱 단계는 필터 컴포넌트로 추상화한다. 데이터는 파이프를 통해 연관된 필터들에게 전달된다. 필터들을 다양하게 재조합하여 시스템을 재구축할 수 있다.

Blackboard A.P. 정의되지 않은 도메인에서의 문제를 해결할때 유용하다. 솔루션에 대한 부분적이거나 대략적인 해법을 수립하기 위해 몇가지 특수한 서브시스템들의 지식을 조합한다.

Broker A.P. 분산 소프트웨어 시스템을 구조화할때 유용하다. 분산 소프트웨어 시스템은 분리된 컴포넌트들이 서로 유기적으로 조합되어 운영되는 시스템으로, 이러한 컴포넌트들 간의 통신을 관장하는 역활을 하는 것이 Broker이다.

Model-View-Controller A.P. 모델은 핵심기능과 데이터를 의미하고 뷰는 기능에 의한 데이터의 표현이며 컨트롤은 사용자의 입력에 대한 처리이다. 뷰와 컨트롤러는 사용자의 인터페이스를 구성하며 사용자 인터페이스와 모델간의 일관성 및 정합성을 보장한다.

Presentation-Abstraction-Control A.P. 계층구조를 이루는 에이전트들이 상호작용하는 소프트웨어 시스템에 대한 패턴. 각각의 에이전트는 하나의 어플리케이션의 특정 부분을 전담하며 에이전트는 프리젠테이션/추상/컨트롤로 구성된다.

Microkernel A.P. 변화하는 시스템에 대한 요구사항을 수용할 수 있도록 하는 패턴. 시스템에서 가장 최하단에 위치하는 핵심 기능을 추출해 내며, 추가된 요구사항에 대해 확장기능으로 정의하여 시스템에 손쉽게 추가할 수 있도록 한다.

Reflection A.P. 소프트웨어 시스템의 구조와 동작을 동적으로 변경할 수 있는 메커니즘을 제공.

Whole-Part D.P. 전체(Whole) 객체를 구성하는 컴포넌트(Part)를 정의한다. Whole 객체를 통해 Part 컴포넌트들의 관계를 맺으며, 이 Whole 객체를 통해서만 Part 컴포넌트와 통신할 수 있다.

Master-Slave D.P. 마스터 컴포넌트는 슬레이브 컴포넌트에게 작업을 분산시켜서 최종적으로 슬레이브로부터 그 결과를 취합한다.

Proxy D.P. 실제 컴포넌트가 아닌 대리자를 앞단에 두어 이 대리자를 통해 실제 컴포넌트와 통신을 한다. 실제 컴포넌트의 위치 추상화, 실제 컴포넌트를 사용하기 위한 인증 등과 같은 전처리는 물론 후처리에 대한 기능 추가가 용이하다.

Command Processor D.P. 사용자의 요청을 하나의 객체로 정의하여 관리하며 Undo/Redo와 같은 처리가 가능하다.

View Handler D.P. 시스템의 모든 뷰를 관리하는 책임을 분리하여 뷰들 간의 관계성과 연관된 작업을 쉽게 처리할 수 있도록 한다.

Forwarder-Receiver D.P. 투명한 IPC를 제공하고 Peer를 분리하기 위해 Forwarder와 Receiver를 분리한다.

Client-Dispatcher-Server D.P. 클라이언트와 서버 사이에 디스패처 레이어를 도입한다. 위치 투명성을 제공하고 클라이언트와 서버간의 통신에 대한 세부적인 구현을 캡출화한다.

Publisher-Subscriber D.P. 서로 긴밀하게 관계를 맺고 있는 컴포넌트들 간의 상태에 대해 정합성을 유지하는데 용이하다. Publisher가 책임을 지고 하나의 변경에 대해 다수의 Subscriber에게 변경을 통지한다.

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 패턴을 구현해보겠습니다.