프로그래밍시에 종종 사용하는 switch ~ case 구문의 가장 큰 단점은

case 구문이 늘어남에 따라 소스코드 가독성이 떨어진다는데에 있다.

물론 각각의 case 구문을 보기좋게 함수화 하거나 함수포인터를 적절히 활용한다면

어느정도 해결은 가능하다.

 

그리고 지금부터 소개할 Command Pattern을 사용해도 기존의 switch ~ case 구문을

보기 좋게 만들수 있다. 뿐만아니라 일련의 명령어들을 일관성있게 실행시키는 용도로도

커맨드 패턴은 자주 사용되어지는 편이다.

 

그렇다고 커맨드 패턴이 장점만 있는건 아니다.

예를 들면 switch ~ case 구문에서 case 구문이 몇 가지 안되는 경우에

커맨드 패턴을 사용하게 되면 오히려 프로그램의 복잡성만 증가한다는 단점이 있다.

 

#define CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
#include <map>

using namespace std;

typedef enum ComamndId
{
    NullCommand = 0,
    LoginCommand,
    LogoutCommand
} CommandId;

class ICommand
{
public:
    ICommand(){}
    virtual ~ICommand(){}

    virtual void Execute() = 0;
    virtual CommandId GetCommandId() = 0;
};

class CNullCommand : public ICommand
{
public:
    CNullCommand(){}
    virtual ~CNullCommand(){}
    
    CommandId GetCommandId() { return NullCommand; }

    void Execute()
    {
        cout << "Null Comamnd " << endl;
    }
};

class CLoginCommand : public ICommand
{
public:
    CLoginCommand(){}
    virtual ~CLoginCommand(){}

    CommandId GetCommandId() { return LoginCommand; }

    void Execute()
    {
        cout << "Login " << endl;
    }
};

class CLogoutCommand : public ICommand
{
public:
    CLogoutCommand(){}
    virtual ~CLogoutCommand(){}

    CommandId GetCommandId() { return LogoutCommand; }
    
    void Execute()
    {
        cout << "Logout " << endl;
    }
};

ComamndId DummyRequest()
{
    return static_cast<CommandId>(rand() % 3);
}

void InitCommands(map<ComamndId, ICommand *> &cmds)
{
    typedef map<ComamndId, ICommand *>::value_type Cmd;

    cmds.insert(Cmd(NullCommand, static_cast<ICommand *>(new CNullCommand)));
    cmds.insert(Cmd(LoginCommand, static_cast<ICommand *>(new CLoginCommand)));
    cmds.insert(Cmd(LogoutCommand, static_cast<ICommand *>(new CLogoutCommand)));
    
    // 나중에 명령어가 추가되면 이곳에 명령어를 추가함.
}

void UnInitCommands(map<ComamndId, ICommand *> &cmds)
{
    map<CommandId, ICommand *>::iterator iter = cmds.begin();

    for ( ; iter != cmds.end(); iter++ )
    {
        ICommand *pCmd = static_cast<ICommand *>(iter->second);
        delete pCmd;
    }

    cmds.clear();
}

ICommand *GetCommand(map<ComamndId, ICommand *> &cmds, CommandId cmdId)
{
    map<CommandId, ICommand *>::iterator iter = cmds.find(cmdId);

    if ( iter == cmds.end() )
    {
        iter = cmds.find(NullCommand);
    }

    return static_cast<ICommand *>(iter->second);
}

void TestCommandPattern()
{
    int i = 0;
    map<ComamndId, ICommand *> cmds;

    InitCommands(cmds);
    
    for ( i = 0; i < 4; i++ )
        GetCommand(cmds, DummyRequest())->Execute();

    UnInitCommands(cmds);
}

int main(int argc, char **argv)
{
    _CrtMemState before, after, diff;

    _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDOUT);
    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDOUT);

    _CrtMemCheckpoint(&before);

    TestCommandPattern();

    _CrtMemCheckpoint(&after);
    if ( _CrtMemDifference(&diff, &before, &after) )
    {
        _CrtMemDumpAllObjectsSince(NULL);
        _CrtMemDumpStatistics(&diff);

        system("pause");
    }

    return 0;
}

[생각할 꺼리]

 

1. switch ~ case 구문은 컴파일 시점에 모든 동작이 정의되어 있어야 하지만,

   Command Pattern은 동적으로 동작을 추가하거나 삭제할 수 있다.

   이와 같은 동작이 왜 가능한지 생각해보자.

 

2. Command Pattern을 switch ~ case 구문을 대체하는 용도로만 사용한다고 생각하고 있으면 안된다.

   그럼, Command Pattern 의 또 다른 응용에는 어떤 것들이 있을지 생각해보자.

 

   [힌트] 배치 명령어, 실행취소

 

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,