이번 시간에는 Observer 패턴, Container 설계의 기술, Iterator 패턴, Visitor 패턴에 대해 알아보겠습니다.

Observer pattern

정의

  • 객체 사이의 1:N의 종속성을 정의 하고 한 객체의 상태가 변하면, 종속된 다른 객체에 통보 되도록 처리
  • 관찰의 대상 -> Subject
  • 통보 받는 대상 -> Observer
  • 데이터 전달 방식 2가지
    • push : 통보시 함수 인자로 값을 같이 전달
    • pull : Subject의 멤버 함수를 통해서 제공

pull 방식 예제

#include <iostream>
#include <vector>
using namespace std;

class Subject;

struct IGraph
{
    virtual void update(Subject*) = 0;

    virtual ~IGraph() {}
};

class Subject
{
    vector<IGraph*> v;
public:
    void attach(IGraph* p) { v.push_back(p);}
    void detach(IGraph* p) { }

    void notify()
    {
        for ( auto p : v)
            p->update(this);
    }
};

class Table : public Subject
{
    int data;
    int color;
public:
    int GetData() { return data;}

    void SetData(int d)
    {
        data = d;
        notify();
    }
};

class PieGraph : public IGraph
{
public:
    virtual void update(Subject* p)
    {
        // table 에 접근해서 data를 꺼내 온다.
        int n = static_cast<Table*>(p)->GetData();

        Draw(n);
    }

    void Draw(int n)
    {
        cout << "Pie Graph : ";

        for ( int i = 0; i < n; i++)
            cout << "*";
        cout << endl;
    }
};

class BarGraph : public IGraph
{
public:
    virtual void update(Subject* p)
    {
        int n = static_cast<Table*>(p)->GetData();

        Draw(n);
    }

    void Draw(int n)
    {
        cout << "Bar Graph : ";

        for ( int i = 0; i < n; i++)
            cout << "+";
        cout << endl;
    }
};

int main()
{
    BarGraph bg;
    PieGraph pg;

    Table t;
    t.attach( &bg);
    t.attach( &pg);

    while( 1 )
    {
        int n;
        cin >> n;
        t.SetData(n);
    }
}

Container 설계의 기술

Generic하고 높은 성능을 갖는 Container 구현

  • thin template 기반 컨테이너(Android OS에서 활용)
#include <iostream>
using namespace std;

struct Node
{
    void* data;
    Node* next;
    Node( void* d, Node* n) : data(d), next(n) {}
};

class slistImp
{
    Node* head = 0;
public:
    void push_front(void* n) { head = new Node(n, head);}
    void* front()           { return head->data;}
};

template<typename T> class slist : public slistImp
{
public:
    inline void push_front(T n) { slistImp::push_front( (void*)n);}
    inline T front()           { return (T)(slistImp::front());}
};

int main()
{
    slist<int> s;

    s.push_front(10);
    s.push_front(20);
    s.push_front(30);
    s.push_front(40);
    s.push_front(50);

    int n = s.front();
}

Iterator pattern

정의

  • 객체의 내부 구조에 상관 없이 동일한 방법으로 요소에 순차적 접근 가능

인터페이스 기반 반복자

#include <iostream>
using namespace std;

template<typename T> struct Node
{
    T   data;
    Node* next;
    Node( const T& d, Node* n) : data(d), next(n) {}
};

template<typename T> struct IEnumerator
{
    virtual ~IEnumerator() {}
    virtual bool MoveNext() = 0;
    virtual T&  GetObject() = 0;
};

template<typename T> class SlistEnumerator : public IEnumerator<T>
{
    Node<T>* current = 0;
public:
    SlistEnumerator( Node<T>* p = 0) : current(p) {}

    virtual bool MoveNext()
    {
         current = current->next;
         return current;
    }
    virtual T&  GetObject() { return current->data; }
};

template<typename T> struct IEnumerable
{
    virtual ~IEnumerable() {}
    virtual IEnumerator<T>* GetEnumerator()  = 0;
};

template<typename T> class slist : public IEnumerable<T>
{
    Node<T>* head = 0;
public:
    virtual IEnumerator<T>* GetEnumerator()
    {
        return new SlistEnumerator<T>( head);
    }

    void push_front(const T& n) { head = new Node<T>(n, head);}
    T  front()                  { return head->data;}
};

template<typename T> void Show( IEnumerator<T>* p )
{
    do
    {
        cout << p->GetObject() << endl;
    } while( p->MoveNext() );
}

int main()
{
    int x[10] = {1,2,3,4,5,6,7,8,9,10};
    int* p1 = x;
    Show( p1);

    slist<int> s;

    s.push_front(10);
    s.push_front(20);
    s.push_front(30);
    s.push_front(40);

    IEnumerator<int>* p = s.GetEnumerator();

    Show( p );
    delete p;
}

STL 방식의 반복자

  • 인터페이스 사용하지 않음
  • 이동 및 접근 함수는 포인터 규칙을 따름(연산자 재정의)
  • 오버헤드를 줄이기 위해 이동 및 접근 함수는 inline 함수로 작성
#include <iostream>
using namespace std;

template<typename T> struct Node
{
    T   data;
    Node* next;
    Node( const T& d, Node* n) : data(d), next(n) {}
};

template<typename T> class slist_iterator
{
    Node<T>* current = 0;
public:
    inline slist_iterator( Node<T>* p = 0) : current(p) {}

    inline slist_iterator& operator++()
    {
         current = current->next;
         return *this;
    }
    inline T&  operator*() { return current->data; }
};
// ++p, *p
template<typename T> class slist
{
    Node<T>* head = 0;
public:
    slist_iterator<T> begin()
    {
        return slist_iterator<T>( head);
    }

    void push_front(const T& n) { head = new Node<T>(n, head);}
    T  front()                  { return head->data;}
};

template<typename T> void Show( T p, T p2 )
{
    do
    {
        cout << *p << endl;
    } while( ++p  != p2);
}

int main()
{
    int x[10] = {1,2,3,4,5,6,7,8,9,10};
    int* p1 = x;
    Show( p1, x+10);

    slist<int> s;

    s.push_front(10);
    s.push_front(20);
    s.push_front(30);
    s.push_front(40);

    slist_iterator<int> p = s.begin();

    cout << *p << endl;
    ++p;
    cout << *p << endl; // 30
}

인터페이스 VS STL 방식 차이

인터페이스 방식 STL 방식
코드 메모리 적음 많음
배열 불가 가능
반복자 꺼내기 스마트포인터 혹은 delete 필요 delete 필요 없음
이동 및 요소접근 가상 함수 기반 연산자 재정의, 인라인 치환

Visitor pattern

모든 요소에 변경을 주고 싶을 때

#include <iostream>
#include <list>
using namespace std;

int main()
{
    list<int> s = { 1,2,3,4,5,6,7,8,9,10};

    // 모든 요소를 2배로 만들고 싶다.
    // 방법1. 외부에서 직접 연산 수행.
    for ( auto& n : s )
        n = n * 2;

    // 방법 2. 멤버 함수로 기능을 제공
    s.twice_all_element();
    s.show_all_element();

    // 방법 3. 방문자 패턴을 사용한다.
    TwiceVisitor<int> tv; // 방문자.
    s.accept(&tv);

    ShowVisitor<int> sv; // 방문자.
    s.accept(&sv);
}

visitor 패턴 정의

  • 객체 구조에 속한 요소에 수행할 오퍼레이션을 정의하는 객체
  • 클래스를 변경하지 않고 새로운 오퍼레이션을 정의 할 수 있게함

복합 객체의 accept 함수는 다양한 방문자를 받을 수 있도록 설계

#include <iostream>
#include <list>
using namespace std;

// 방문자(visitor)의 인터페이스
template<typename T> struct IVisitor
{
    virtual void visit(T& elem) = 0;
    virtual ~IVisitor() {}
};

template<typename T> class TwiceVisitor : public IVisitor<T>
{
public:
    virtual void visit(T& elem) { elem = elem * 2;}
};

template<typename T> class ShowVisitor : public IVisitor<T>
{
public:
    virtual void visit(T& elem) { cout << elem << endl;}
};
//------------------------
// 방문의 대상의 인터페이스
template<typename T> struct IAcceptor
{
    virtual void accept( IVisitor<T>* p) = 0;
    virtual ~IAcceptor()  {}
};

template<typename T> class List : public list<T>, public IAcceptor<T>
{
public:
    using list<T>::list; // c++11 생성자 상속
    virtual void accept( IVisitor<T>* p)
    {
        // 모든 요소를 방문자에게 전달.
        for( auto& e : *this)
            p->visit(e);
    }
};

template<typename T> class TripleVisitor : public IVisitor<T>
{
public:
    virtual void visit(T& elem) { elem = elem * 3;}
};

int main()
{
    List<int> s = { 1,2,3,4,5,6,7,8,9,10};

    TwiceVisitor<int> tv;
    s.accept(&tv);

    TripleVisitor<int> trv;
    s.accept(&trv);

    ShowVisitor<int> sv;
    s.accept(&sv);
}

여러 메뉴를 관리하는 객체의 Visit 패턴 적용 예제

#include <iostream>
#include <string>
#include <vector>
using namespace std;

//---------------------------
class BaseMenu;
class MenuItem;
class PopupMenu;

// 방문자의 인터페이스
struct IMenuVisitor
{
    virtual ~IMenuVisitor() {}

    virtual void visit(BaseMenu* p) = 0;
    virtual void visit(MenuItem* p) = 0;
    virtual void visit(PopupMenu* p) = 0;
    virtual void visit(PopupMenu* p) = 0;
};

struct IAcceptor
{
    virtual ~IAcceptor() {}
    virtual void accept(IMenuVisitor* p) = 0;
};
//-----------------------------------------------

class BaseMenu : public IAcceptor
{
    string title;
public:
    BaseMenu( string s) : title(s) {}
    void setTitle(string s) { title = s;}
    string getTitle() const { return title;}

    virtual void command() = 0;
};

class MenuItem : public BaseMenu
{
    int id;
public:
    virtual void accept(IMenuVisitor* p)
    {
        p->visit(this);
    }

    MenuItem(string s, int n) : BaseMenu(s),  id(n) {}

    virtual void command()
    {
        cout << getTitle() << endl;
        getchar();
    }
};

class PopupMenu : public BaseMenu
{
    vector<BaseMenu*> v;
public:
    PopupMenu( string s) : BaseMenu(s) {}

    void addMenu(BaseMenu* p) { v.push_back(p);}

    virtual void command()
    {
        while( 1 )
        {
            system("cls");

            int sz = v.size();

            for ( int i = 0; i < sz; i++)
            {
                cout << i + 1 << ". " << v[i]->getTitle() << endl;
            }

            cout << sz + 1 << ". << back " << endl;

            //------------------------------
            int cmd;
            cout << "choose menu >> ";
            cin >> cmd;

            if ( cmd < 1 || cmd > sz + 1 ) // 잘못된 입력
                continue;

            if ( cmd == sz + 1 )
                break;


            // 선택된 메뉴 실행..
            v[cmd-1]-> command(); // 핵심.. !
        }

    }
    virtual void accept(IMenuVisitor* p)
    {
        p->visit(this);

        for ( auto m : v)
            m->accept(p);
    }
};

class MenuTitleChangeVisitor : public IMenuVisitor
{
public:
    virtual void visit(BaseMenu* p) {}
    virtual void visit(MenuItem* p) {}
    virtual void visit(PopupMenu* p)
    {
        // popupmenu 의 타이틀을 변경한다.
        string s = p->getTitle();
        s = "[ " + s + " ]";

        p->setTitle(s);
    }
};

class EraseTitleChangeVisitor : public IMenuVisitor
{
public:
    virtual void visit(BaseMenu* p) {}
    virtual void visit(MenuItem* p) {}
    virtual void visit(PopupMenu* p)
    {
        p->setTitle("...");
    }
};

int main()
{
    PopupMenu* p1 = new PopupMenu("MENUBAR");

    p1->addMenu( new PopupMenu("SCREEN"));
    p1->addMenu( new PopupMenu("SOUND"));
    p1->addMenu( new MenuItem("power off", 11));

    //---------------------
    MenuTitleChangeVisitor mtcv;
    p1->accept(&mtcv);

    EraseTitleChangeVisitor etcv;
    p1->accept(&etcv);
    // 1. 메뉴 (복합객체)는 accept 가 필요
    // 2. 방문자 인터페이스 필요..

    p1->command();
}