Composite Pattern
객체들을 트리구조로 구성하여 부분과 전체를 나 나태는 계층구조로 만들 수 있다.
개별 객체와 복합객체를 구별하지 않고 동일한 방법 다룰 수 있다.
이러한 구조를 흔히 윈도우에서 우클릭을 하면 볼 수 있는 팝업 메뉴를 예로 구현해본다.
구성 : 선택했을때선택했을 때 하위 메뉴를 열어주는 메뉴(PopupMenu), 선택했을 때 어떤 일을 실행하는 메뉴(MenuItem)
설계 : 이 구성을 객체지향으로 설계하면 PopupMenu, MenuItem 클래스로 만들 텐데 PopupMenu에는 하위 항목으로 실행을 위한 MenuItem을 가질 수 있고, 또 다른 하위 메뉴로 PopupMenu를 가질 수도 있다. 이 둘을 모두 담기 위해서는 기반 클래스 BaseMenu가 있어야 하겠다.
위 다이어그램의 구성으로 다음과 같이 BaseMenu와 MenuItem을 구현할 수 있겠다.
#include <iostream>
#include <string>
#include <vector>
#include <stdio.h>
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:
// 생성자 id 초기화, 기반클래스의 title 초기화
// -> 파생클래스를 만들었을때 기반클래스의 기본 생성자가 없다면 생성자도 불려져야한다.
MenuItem(string s, int n) : BaseMenu(s), id(n) {}
virtual void command()
{
cout << getTitle() << endl;
getchar();
}
};
int main()
{
MenuItem m("sound", 11);
return 0;
}
이제 다음 단계로 PopupMenu를 구현한다.
#include <iostream>
#include <string>
#include <vector>
#include <stdio.h>
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) {}
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(true)
{
// 화면 지우기
system("cls");
// 하위 메뉴의 개수
int size = v.size();
for(int i = 0; i < size; i++)
{
// 번호 출력 0이 아닌 1부터 출력, 타이틀 출력
cout << i + 1 << ". " << v[i]->getTitle() << endl;
}
// 마지막 메뉴로 이전으로 돌아가는 메뉴 출력
cout << size + 1 << ". 상위메뉴로 " << endl;
// 사용자 입력 (메뉴 선택)
int cmd = 0;
cout << "메뉴를 선택하세요 >> ";
cin >> cmd;
// 잘못된 입력 -> 다시 반복
if( cmd < 1 || cmd > size + 1)
continue;
// 이전 메뉴로 돌아가기
if( cmd == size + 1)
break;
// 선택된 메뉴 실행 (1번부터 출력 했으므로 -1)
// 다형성 : 메뉴가 팝업인지 아이템인지 확인할 필요없이 스스로 실행.
v[cmd-1]->command();
}
}
};
int main()
{
MenuItem m("sound", 11);
return 0;
}
PopupMenu가 완성되었다. 이제 MenuItem과 PopupMenu를 사용하여 Menu를 구성해보자.
int main()
{
//MenuItem m("sound", 11);
PopupMenu* menubar = new PopupMenu("MenuBar"); //Composite, node (복합객체)
PopupMenu* pm1 = new PopupMenu("화면설정");
PopupMenu* pm2 = new PopupMenu("소리설정");
MenuItem* m1 = new MenuItem("정보확인", 11);
menubar->addMenu(pm1);
menubar->addMenu(pm2);
menubar->addMenu(m1);
menubar->addMenu(new MenuItem("해상도변경", 21));
menubar->addMenu(new MenuItem("명암변경", 22));
menubar->addMenu(new MenuItem("음량조절", 31)); // Leaf (개별객체)
// 시작하기
menubar->command();
return 0;
}
위의 구성은 다음과 같다.
이제 하위 메뉴를 얻기 위한 getSubMenu() 도 추가 한다. 이 경우 getSubmenu()를 파생 클래스에만 넣을 것인가? 기반 클래스에 넣을 것인가? getSubMenu()의 경우 MenuItem에는 필요 없을 것이다. 즉 공통적인 속성이 아닐 것이다. 그럼에도 getSubMenu는 BaseMenu에 있는 것이 좋다.
#include <iostream>
#include <string>
#include <vector>
#include <stdio.h>
//#include <stdlib.h>
using namespace std;
class BaseMenu
{
string title;
public:
BaseMenu(string s):title(s) {}
string getTitle() const {return title;}
// 기반클래스가 해줄것이 없다하더라도 공통적인 특징이라면 순수 가상함수를 만들어준다.
virtual void command() = 0;
// getSubMenu의 경우 MenuItem에는 필요가 없을 것이다. 그럼에도 getSubMenu는 부모클래스에 있어야 한다.
//virtual BaseMenu* getSubMenu(int index) = 0; -> 이렇게 하면 MenuItem에 불필요한 구현이 들어가야 하므로 X
virtual BaseMenu* getSubMenu(int index)
{
//throw "Unsupported funciton";
return 0;
}
void addMenu(BaseMenu* p)
{
throw "Unsupported funciton";
}
};
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(true)
{
// 화면 지우기
//system("cls");
//system("clear");
// 하위 메뉴의 개수
int size = v.size();
for(int i = 0; i < size; i++)
{
// 번호 출력 0이 아닌 1부터 출력, 타이틀 출력
cout << i + 1 << ". " << v[i]->getTitle() << endl;
}
// 마지막 메뉴로 이전으로 돌아가는 메뉴 출력
cout << size + 1 << ". 상위메뉴로 " << endl;
// 사용자 입력 (메뉴 선택)
int cmd = 0;
cout << "메뉴를 선택하세요 >> ";
cin >> cmd;
// 잘못된 입력 -> 다시 반복
if( cmd < 1 || cmd > size + 1)
continue;
// 이전 메뉴로 돌아가기
if( cmd == size + 1)
break;
// 선택된 메뉴 실행 (1번부터 출력 했으므로 -1)
// 다형성 : 메뉴가 팝업인지 아이템인지 확인할 필요없이 스스로 실행.
v[cmd-1]->command();
}
}
};
int main()
{
//MenuItem m("sound", 11);
PopupMenu* menubar = new PopupMenu("MenuBar"); //Composite, node (복합객체)
PopupMenu* pm1 = new PopupMenu("화면설정");
PopupMenu* pm2 = new PopupMenu("소리설정");
MenuItem* m1 = new MenuItem("정보확인", 11);
menubar->addMenu(pm1);
menubar->addMenu(pm2);
menubar->addMenu(m1);
menubar->addMenu(new MenuItem("해상도변경", 21));
menubar->addMenu(new MenuItem("명암변경", 22));
menubar->addMenu(new MenuItem("음량조절", 31)); // Leaf (개발객체)
//menubar->getSubMenu(1); // ->소리설정 출력
BaseMenu* p = menubar->getSubMenu(1)->getSubMenu(0);
menubar->getSubMenu(1)->addMenu(new MenuItem("AA", 100));
// 시작하기
menubar->command();
return 0;
}
Composite Pattern
객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들 수 있다
개별 객체와 복합 객체를 구별하지 않고 동일한 방법으로 다룰 수 있다.