Design pattern in C++ - 재귀적 포함[3]
이번시간에는 Composite 패턴, Decorator 패턴에 대해 살펴보겠습니다.
1. Composite 패턴
개념
- 개체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층 구조 표현
- 개별 객체와 복합 객체를 구별하지 않고 동일한 방법(다형성)으로 다룰 수 있음
Composite 패턴을 활용한 메뉴 구조 만들기
- 상속 관계를 통해 구현
- 파생 클래스에서 포함 관계를 표현하는 컨테이너 정의시 기반 클래스 형태로 선언(모든 타입의 파생 클래스를 담을 수 있음)
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class BaseMenu
{
string title;
public:
BaseMenu( string s) : title(s) {}
string getTitle() const { return title;}
virtual BaseMenu* getSubMenu(int idx)
{
throw "unsupported function.."
return 0;
}
void addMenu(BaseMenu* p)
{
throw "unsupported function..";
}
virtual void command() = 0;
};
class MenuItem : public BaseMenu
{
int id;
public:
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 << ". 상위 메뉴로" << endl;
//------------------------------
int cmd;
cout << "메뉴를 선택하세요 >> ";
cin >> cmd;
if ( cmd < 1 || cmd > sz + 1 )
continue;
if ( cmd == sz + 1 )
break;
// 다형성
v[cmd-1]-> command();
}
}
BaseMenu* getSubMenu(int idx)
{
return v[idx];
}
};
int main()
{
PopupMenu* menubar = new PopupMenu("mebuBar");
PopupMenu* pm1 = new PopupMenu("화면설정");
PopupMenu* pm2 = new PopupMenu("소리설정");
MenuItem* m1 = new MenuItem("정보 확인", 11);
menubar->addMenu( pm1 );
menubar->addMenu( pm2 );
menubar->addMenu( m1 );
pm1->addMenu( new MenuItem("해상도변경", 21));
pm1->addMenu( new MenuItem("명암 변경", 22));
pm2->addMenu( new MenuItem("음량 조절", 31));
BaseMenu* p = menubar->getSubMenu(1)->getSubMenu(0);
menubar->getSubMenu(1)->addMenu( new MenuItem("AA", 100));
// 시작하려면
menubar->command();
}
2. Composite 패턴과 Event 처리
메뉴 클릭시 처리해야 할 코드를 작성하는 방법
-
메뉴 선택시 하는 일을 가상함수로 분리
- 파생 클래스의 수가 많아짐
#include "menu.hpp" class MenuItem : public BaseMenu { int id; public: MenuItem(string s, int n) : BaseMenu(s), id(n) {} virtual void command() { // 변해야 하는 것을 가상함수로. doCommand(); } virtual void doCommand() {} }; class AddStudentMenu : public MenuItem { public: using MenuItem::MenuItem; // 생성자 상속.. virtual void doCommand() { cout << "Add Student" << endl; } }; class RemoveStudentMenu : public MenuItem { public: using MenuItem::MenuItem; virtual void doCommand() { cout << "Remove Student" << endl; } }; int main() { AddStudentMenu m1( "Add Student " , 11); RemoveStudentMenu m2( "Remove Student " , 12); m1.command(); m2.command(); }
-
Interface를 포함시켜 다른 클래스에 처리를 위임(Listener 개념)
#include "menu.hpp" // 메뉴 메세지를 처리하려면 아래 인터페이스를 정의 해야 한다. struct IMenuListener { virtual void doCommand( int id) = 0; virtual ~IMenuListener() {} }; class MenuItem : public BaseMenu { int id; IMenuListener* pListener = 0; public: void setListener(IMenuListener* p) { pListener = p;} MenuItem(string s, int n) : BaseMenu(s), id(n) {} virtual void command() { // 변하는 것을 다른 클래스로..! if ( pListener != 0 ) pListener->doCommand( id ); } }; class Dialog : public IMenuListener { public: virtual void doCommand(int id) { //cout << "Dialog doCommand" << endl; switch( id ) { case 11: cout << "11" << endl; break; case 12: cout << "12" << endl; break; } } }; int main() { Dialog dlg; MenuItem m1( "Add Student " , 11); MenuItem m2( "Remove Student " , 12); m1.setListener(&dlg); m2.setListener(&dlg); m1.command(); m2.command(); }
-
객체가 아닌 함수(함수포인터, 람다) 를 연결
- 멤버 함수 포인터를 받기 위해 템플릿으로 처리
#include <iostream> using namespace std; void foo() { cout << "foo" << endl;} class Dialog { public: void Close() { cout << "Dialog Close" << endl;} }; //----------------------- struct IAction { virtual void Execute() = 0; virtual ~IAction() {} }; class FunctionAction : public IAction { typedef void(*FP)(); FP handler; public: FunctionAction(FP f) : handler(f) {} virtual void Execute() {handler();} }; template<typename T> class MemberAction : public IAction { typedef void(T::*FP)(); FP handler; T* target; public: MemberAction(FP f, T* obj) : handler(f), target(obj) {} virtual void Execute() { (target->*handler)();} }; // 함수 템플릿 template<typename T> MemberAction<T>* action( void(T::*f)(), T* obj) { return new MemberAction<T>( f, obj); } FunctionAction* action( void(*f)() ) { return new FunctionAction( f); } int main() { Dialog dlg; IAction* p1 = action(&foo); IAction* p2 = action(&Dialog::Close, &dlg); p1->Execute(); p2->Execute(); }
-
위 action을 표준의 bind로 처리 할 수 있음(C++11)
#include <iostream> #include <functional> using namespace std; void foo() { cout << "foo" << endl;} void goo(int n) { cout << "goo : " << n << endl;} class Dialog { public: void Close() { cout << "Dialog Close" << endl;} }; int main() { function<void()> f; f = &foo; f(); // foo 호출 Dialog dlg; f = bind(&Dialog::Close, &dlg); // action(&Dialog::Close, &dlg) f(); // dlg.Close() f = bind(&goo, 5); f(); // goo(5) }
3. Decorator pattern
개념
- 기존의 있던 동작은 유지하되, 새로운 기능이 추가 되야 할 경우 사용하는 패턴
- 런타임에 객체에 기능을 추가
- 기능 추가 대상 객체 -> Concrete Component
- 추가 될 기능 객체 -> Concrete Decorator
예제
- 미사일을 쏘는 객체
#include <iostream>
using namespace std;
// 우주선과 기능추가객체의 공통의 기반 클래스
struct Component
{
virtual void Fire() = 0;
virtual ~Component() {}
};
class SpaceCraft : public Component
{
int color;
int speed;
public:
void Fire() { cout << "Space Craft : ----------" << endl;}
};
//---------------
// 기능 추가 클래스의 공통의 기반 클래스
class IDecorator : public Component
{
Component* craft;
public:
IDecorator( Component* p) : craft(p) {}
void Fire() { craft->Fire();}
};
class LeftMissile : public IDecorator
{
public:
LeftMissile( Component* p) : IDecorator(p) {}
void Fire()
{
IDecorator::Fire();
cout << "Left Missile : >>>>>>>>" << endl;
}
};
class RightMissile : public IDecorator
{
public:
RightMissile( Component* p) : IDecorator(p) {}
void Fire()
{
IDecorator::Fire();
cout << "Rigth Missile : >>>>>>>>" << endl;
}
};
int main()
{
SpaceCraft sc;
LeftMissile lm(&sc);
RightMissile rm(&lm);
rm.Fire();
}