Design pattern in C++ - 통보, 열거, 방문[5]
이번 시간에는 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();
}