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 void command() = 0;
};
class MenuItem : public BaseMenu
{
int id;
public:
MenuItem(string s, int n) : BaseMenu(s), id(n) {}
// BaseMenu에 디폴트 생성자가 없기에 BaseMenu(s)를 반드시 넣어 줘야함.
virtual void command() override {
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() override {
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();
// 핵심 -> 다형성으로 MenuItem, PopupMenu의 command를 모두 처리가능
}
}
};
int main()
{
PopupMenu* menubar = new PopupMenu("MenuBar");
PopupMenu* pm1 = new PopupMenu("화면설정");
PopupMenu* pm2 = new PopupMenu("소리설정");
MenuItem m1("정보 확인", 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);
// 시작
menubar->command();
}
아래와 같은 기능을 추가하고 싶다면?
int main()
{
PopupMenu* menubar = new PopupMenu("MenuBar");
PopupMenu* pm1 = new PopupMenu("화면설정");
PopupMenu* pm2 = new PopupMenu("소리설정");
MenuItem m1("정보 확인", 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->command();
}
class PopupMenu : public BaseMenu
{
vector<BaseMenu*> v;
public:
PopupMenu(string s) : BaseMenu(s) {}
void addMenu(BaseMenu* p) { v.push_back(p); }
BaseMenu* getSubMenu(int idx) { return v[idx]; }
// PopupMenu에만 넣으면 될까? -> Nope, BaseMenu에 넣어줘야함 SubMenu가 PopupMenu일지 MenuItem일지 모르기때문
class BaseMenu
{
string title;
public:
BaseMenu(string s) : title(s) {}
string getTitle() const { return title; }
virtual void command() = 0;
virtual BaseMenu* getSubMenu(int idx)
{
throw "unsupported function";
// MenuItem에서 호출시 throw를 던저버린다.
return 0;
}
};
기능 추가) 각 메뉴에서 새로운 기능을 추가하고자 한다면?
#include "menu.hpp"
class MenuItem : public BaseMenu
{
int id;
public:
MenuItem(string s, int n) : BaseMenu(s), id(n) {}
virtual void command()
{
// 여기는 실제 동작이 들어가는데 ...
// 각 메뉴마다 다른 일을 수행해야한다. -> 어떻게 만들지??
// 방법 1. - 가상 함수 처리
doCommand();
}
virtual void doCommand() {}
};
class AddStudentMenu : public MenuItem
{
public:
using MenuItem::MenuItem; // 생성자 상속
virtual void doCommand() { cout << "Add Student" << endl; }
};
int main()
{
AddStudentMenu m1("Add Student ", 11);
m1.command();
}
그런데 이런 방식은 메뉴가 50개라면 50개의 클래스를 만들어야한다.
struct IMenuListner
{
virtual void doCommand(int id) = 0;
virtual ~IMenuListner() {}
};
class MenuItem : public BaseMenu
{
int id;
IMenuListner* pListner = 0;
public:
MenuItem(string s, int n) : BaseMenu(s), id(n) {}
void setLisnter(IMenuListner* p) {pListner = p;}
virtual void command()
{
// 방법 2. - 변하는 것을 다른 클래스로 뽑자.
if(pListner != 0)
pListner->doCommand(id);
}
};
class Dialog : public IMenuListner
{
public:
virtual void doCommand(int id)
{
switch(id)
{
case 11: cout << "Diglog doCommand : 11" << endl; break;
case 12: cout << "Diglog doCommand : 12" << endl; break;
defualt: break;
}
}
}
int main()
{
Diglog dlg;
MenuItem m1("Add Student", 11);
m1.setLisnter(&dlg);
m1.command();
}
위 방식은 switch문이 너무 커지게 될꺼 같은데??
그래서 나온게 function 템플릿!
우선, 일반/멤버 함수 포인터를 하나의 변수에 담고 싶다면
// 일단은 이 부분을 먼저 이해하고 들어가자
#include <iostream>
using namespace std;
void foo() { cout << "foo" << endl; }
class Dialog{
public:
void Close() { cout << "Dialog Close" << endl; }
};
int main()
{
// 이런게 하고싶다 void(*f1)()에는 &foo만 넣고 있고
// void(Dialog::*f2)()에는 &Dialog::Close를 넣고있는데
// 그냥 상관없이 &foo, &Dialog::Close 를 넣고 싶다면?
// 정리하면 일반 함수 포인터(&foo)나 멤버 함수 포인터(&Dialog::Close)에 상관없이 모든 함수포인터를 넣고싶다는 말
void(*f1)() = &foo;
void(Dialog::*f2)() = &Dialog::Close;
}
struct IAction
{
virtual void Execute() = 0;
virtual ~IAction() {}
};
class FuctionAction : public IAction
{
typedef void(*FP)();
FP handler;
public:
FuctionAction(FP f) : handler(f) {}
virtual Execute() { handler(); }
};
template<typename T>
class MemberAction : public IAction
{
typedef void(T::*FP)();
FP handler;
public:
MemberAction(FP f, T* obj) : handler(f), target(obj) {}
virtual Execute() { (target->*handler)(); }
};
int main()
{
Dialog dlg;
IAction* p = new FunctionAction(&foo);
IAction* p2 = new MemberAction<Dialog>(&Dialog::Close, &dlg);
p->Execute(); // foo 실행
p2->Execute(); // Close 실행
}
코드를 좀 더 간단히 해보자.
// 우선 아래를 이해해보자.
template<typename T> void square(T a) { return a * a; }
square<int>(3);
square(3); // 3을 보고 컴파일러가 타입을 추론
list<int> s(10, 3); // 클래스 템플릿은 타입추론이 안됨.
위 방법과 같이 타입추론을 컴파일러가 해주는데 new MemberAction<Dialog>(&Dialog::Close, &dlg);
에서도 타입 추론이 안되나??
// 함수 템플릿 -> 타입 추론 가능
template<typename T>
MemberAction<T>* action(void(T::*f)(), T* obj)
{
return new MemberAction<T>(f, obj);
}
int main()
{
Dialog dlg;
IAction* p = new FunctionAction(&foo);
// IAction* p2 = new MemberAction<Dialog>(&Dialog::Close, &dlg);
IAction* p2 = action(&Dialog::Close, &dlg); // ok
p->Execute(); // foo 실행
p2->Execute(); // Close 실행
}
코드에 일관성을 부여해보자.
// 함수 템플릿 -> 타입 추론 가능
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* p = new FunctionAction(&foo);
IAction* p = action(&foo);
// IAction* p2 = new MemberAction<Dialog>(&Dialog::Close, &dlg);
IAction* p2 = action(&Dialog::Close, &dlg); // ok
p->Execute(); // foo 실행
p2->Execute(); // Close 실행
}
이런 기능을 C++ 자체에서 제공한다.
function 템플릿
C++11 부터 지원되는 일반화된 함수 포인터 역할 템플릿
#include <iostream>
#include <functional>
using namespace std;
void foo() { cout << "foo" << endl; }
void goo(int) { cout << "goo" << 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);
f(); // dlg.Close() 호출
f = bind(&goo, 5);
f(); // goo(5) 호출
}