본문 바로가기
카테고리 없음

[C++]Design Pattern - Composite Pattern

by Mulder5 2020. 12. 13.
반응형

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

객체들을 트리 구조로 구성하여 부분과 전체를 나타내는 계층구조로 만들 수 있다
개별 객체와 복합 객체를 구별하지 않고 동일한 방법으로 다룰 수 있다.

 

반응형