이번시간에는 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 처리

메뉴 클릭시 처리해야 할 코드를 작성하는 방법

  1. 메뉴 선택시 하는 일을 가상함수로 분리

    • 파생 클래스의 수가 많아짐
    #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();
    }
    
  2. 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();
    }
    
  3. 객체가 아닌 함수(함수포인터, 람다) 를 연결

    • 멤버 함수 포인터를 받기 위해 템플릿으로 처리
    #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();
    }
    
  4. 위 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();
}