프로그래밍시에 종종 사용하는 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 의 또 다른 응용에는 어떤 것들이 있을지 생각해보자.

 

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

 

블로그 이미지

맨오브파워

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

,

싱글턴 패턴(Singleton pattern)이란 어떤 클래스의 인스턴스를 단 하나만 허용하고,

이 인스턴스에 접근할 수 있는 전역적인 접근을 가능하게 하는 패턴이다.

 

즉, 응용프로그램내에서 단 하나뿐인 전역 클래스를 만들고 싶을 때,

싱글턴 패턴을 사용하게 된다.

 

나 같은 경우 주로 프로그램 실행 초기에,

전역적인 환경설정 값을 로드하는 시점에서

싱글턴 패턴을 많이 사용하는 편이다.

 

#include <iostream>
#include "Singleton.h"

using namespace std;

int main ( int argc, char **argv )
{   
    CSingleton *pInstance = CSingleton::GetInstance();

    pInstance->m_nAge = 100;
    pInstance->m_strName = "James";

    CSingleton *p = CSingleton::GetInstance();

    cout << "age " << p->m_nAge << endl;
    cout << "name " << p->m_strName << endl;

    return 0;
}

////////////////////////////////////////////////////////////////////////////////

#ifndef SINGLETON_H
#define SINGLETON_H

#include <string>

using namespace std;

class CSingleton  
{
private:
    CSingleton(){};
    virtual ~CSingleton(){};
    void Init();

public:
    static CSingleton *GetInstance();   

public:
    int m_nAge;
    string m_strName;

private:
    static CSingleton *m_pInstance;
};

#endif   // end of SINGLETON_H

////////////////////////////////////////////////////////////////////////////////

#include "Singleton.h"

CSingleton *CSingleton::m_pInstance = 0;

CSingleton* CSingleton::GetInstance()
{
    if ( !m_pInstance )
    {
        m_pInstance = new CSingleton;
        m_pInstance->Init();
    }

    return m_pInstance;
}

void CSingleton::Init()
{
    m_nAge = 0;
    m_strName = "";
}

 

[생각할 꺼리들]

 

1. 멀티쓰레드 환경에서 Instance가 생성되어 있지 않은 시점에서 동시에 GetInstance()를 호출했을 때의 문제점

2. 인스턴스에 할당된 메모리가 릴리즈 되는 시점

블로그 이미지

맨오브파워

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

,

 

블로그 이미지

맨오브파워

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

,