'패턴'에 해당되는 글 2건

  1. 2005.01.13 오늘 나눈 대화 한조각
  2. 2004.01.30 스테이트
dev.log2005. 1. 13. 18:46

[먕] //네트웍 패킷 파싱.
  int header=packet.header;
  Message * pMsg;
  switch(header)
  {
  case HELLO_MSG:
    pMsg=new HelloMsg();
    break;
  case CHAT_MSG: 
    pMsg=new ChatMsg();
    break;
  }
[먕] 잠시
[먕] 위의 예를 보자고
[먕] HelloMsg,ChatMsg 는 모두 Msg 를 상속 받았다고 하고
[먕] 네트웍 패킷을 위처럼 파싱하고 있는데
(엄) Message 겠지;
(엄) 여튼.
[먕] 저런 switch 문을 쓰고 싶지 않고.
[먕] 해당 키 값에 따라 동적으로 클래스 인스턴스를 생성하고 싶으면
[먕] 어케 해야 되나?
(엄) 어차피 어딘가에는 스위치나. 테이블이 들어 가야 해 -_-
(엄) 뭐 그럴때 쓰는게
(엄) 프로토타입 패턴이긴 한데
(엄) 어차피 스위치나 테이블 둘중의 하나가
(엄) 코드에 포함되는건 피할 수 없지 ㅡ_-
(엄) 나도 저 문제로 열라 궁리 많이 했거덩 -_-
[먕] 음...
[먕] c로는 불가능 한거군.
[먕] java 에서는 저런 switch 문 없이
[먕] 동적으로 생성자 맵핑이 되거덩
(엄) 어뜨케?
[먕] 해쉬테이블이 구조체가 하나있고
[먕] hash.registerDecoder(HELLO_MSG,HelloMsg.class);
[먕] hash.registerDecoder(HCHAT_MSG,ChatMsg.class);
(엄) 그게
(엄) 프로토 타입이자너 -_-
[먕] 패턴이름은 모르겠고.
(엄) 뭐. 그렇게 하려면.
(엄) Message 클래스에
(엄) Message* Message::clone()
(엄) 등의 메소드를 선언하고.
(엄) Message* ChatMsg::clone() { return new ChatMsg( *this ); }
(엄) Message* HelloMsg::clone() { return new HelloMsg( *this ); }
(엄) 그리고 각 객체의 프로토 타입을 해쉬테이블에 등록하면 되지
[먕] 흑... 그건 아냐..
[먕] 잠시.
(엄) 어
[먕] 그럼 위에 처럼 할려면
[먕] 메시지 디코더 인스턴스가 하나씩 생성되어 버리는거 아냐?
(엄) 어차피 니가 쓴 자바 코드도
(엄) 해쉬테이블에
(엄) 타입태그와 타입클래스를
(엄) 등록해 놓는거 아냐
[먕] 자바에서는
[먕]
  NetMessageHandlerManager manager=getMessageHandlerManager();
  manager.registerHandler(DefaultChatMessage.makeIdentifier(ChatProtocol.LOGIN_SUCCESS),new UnSupportedMessageHandler());
  manager.registerHandler(DefaultChatMessage.makeIdentifier(ChatProtocol.PLAIN_MESSAGE),new ChatMessageHandler());
  manager.registerHandler(DefaultChatMessage.makeIdentifier(ChatProtocol.CHAT_MESSAGE_RECEIVE),new C
[먕] 이렇게 해놓으면 타입태그+클래스생성자 들이 등록되는거고
[먕] 위의 프로토타입 패턴을 쓰면.
[먕] 타입태그+클래스 인스턴스가 등록되고
(엄) 그르치
[먕] 그나마 깔끔하다.
(엄) 뭐 정 시르면
(엄) 그르니까
(엄) 해당 메시지의 인스턴스가 생기는게 정 시르면
[먕] 시로
(엄) 해당 클래스의
(엄) 오브젝트 팩토리를 등록해 놓든가 -_-
[먕] 잠시 고로케 한번 해볼께
[먕] 근데.
(엄) 어
[먕] 그렇게 하면 마찬가지로
[먕] 타입태그+팩토리 오브젝트가 맵핑되는거 아닌가 -_-
(엄) 그러치
(엄) 어차피.
(엄) 자바 코드도
(엄) Class 클래스의 객체가 등록되는 거자너
(엄) 별로 다를건 없는거 같은데? -_-
[먕] 잠시 비교 들어갔음.;
(엄) 웅
[먕] 근데
[먕] c 에서 팩토리 구현은 어떤 식으로 되는거야 ?
(엄) C에서?
[먕] c++ 이나
(엄) C에서는 좀 많이 돌아가야 되고 -_-
(엄) C++은.. 대개는
class abstract_factory
{
    object* produce() = 0;
};
class chat_factory : public abstract_factory
{
    object* produce() { return new chat_msg(); }
};
(엄) 뭐 이런 식으로 하지
[먕] 아 -_-
(엄) 아
(엄) virtual 빼먹;
class abstract_factory
{
    virtual object* produce() = 0;
};
[먕] 비지니스 오브젝트 생성 없이
[먕] 팩토리 오브젝트 생성으로 대신하는군
(엄) 어
(엄) 내가 그랬자너 -_-
(엄) 어차피 어딘가에서는
(엄) 테이블에 등록되던가 스위치가 들어가던가
(엄) 둘중의 하나가 불가결하다고 -_-
[먕] 절케 하려면
[먕] 또다시 N 개의 팩토리 클래스가 필요하네 -_-
(엄) 어
[먕] 아햏햏
(엄) 뭐, 물론 비즈니스 오브젝트의 불필요한 생성은 막을 수 있고....
(엄) 팩토리 패턴이 가지는 모든 장점도....
(엄) 차후에 적용할 수 있거찌 ㅡ_-
[먕] 아아...자바가 좋아~ =.=
(엄) 자바도 다를바가 없는거 같은데? -_-
(엄) 자바도
(엄) 어차피 클래스 로더가
(엄) 클래스 로딩하면
(엄) Class 클래스의 객체를 만들어 놓자너 -_-
[먕] 오브젝트의 동적타입을 실시간에 알 수 있으므로
[먕] C 보다는 져아~ =.=
(엄) 어. C++도 가능해
(엄) 컴파일할 때 RTTI 켜놓고 컴파일하면
(엄) typeid()던가
(엄) 하는 연산자를 쓸 수 있지;
[먕] RTTI 가 뭐야 ?
[먕] 첨들어 =.=
(엄) runtime type information
[먕] 오오....
[먕] 마이 컸네 C
[먕] =3
(엄) C++98 부터 있던 표준이야 ㅡ_-
[먕] 한번도 본적 없는걸 ㅡ.ㅜ
(엄) http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/_pluslang_typeid_operator.asp
[먕] 저걸로 컴팔이 하면
[먕] 클래스 필드에 뭔가가 추가 되는거냐
(엄) 당연히 뭔가가 추가 되거찌?
[먕] 그럼 예상치 못한 문제도 생길 수 있겠군 -_-
[먕] 여튼..
[먕] 별게 다있군 오..
(엄) 뭐 그래서 로우-레벨한 걸 좋아하는. 대다수의 C++유저는..
(엄) RTTI잘 안쓰지 ㅡ_-
[먕] 특히 네트웍 플그램 에서도 =.=
(엄) 어
[먕] 니가 말한 팩토리 이용한 방법이
[먕] 가장.. 적합한것 같다
(엄) 내 생각도 -_-
[먕] OKI DOKI
(엄) 수고~
[먕] 어 수고~


 ==================================================================

결국, 가능한 최선의 해결책은 결국엔 하나로 귀결된다고나 할까.

저기서 약간의 첨언을 하자면, 각 팩토리 클래스는 템플릿으로 구현하면 약간의 손품을 덜 수 있다..

template <class T>
class concrete_factory : public abstract_factory
{
public:
    object* produce() { return new T(); }
};

위에서 T가 object에서 상속받기만 한다면, 다음과 같이 팩토리 패턴을 그대로 쓸 수 있다.

hash_table.register( ID_TYPE1, new concrete_factory<type1>() );
hash_table.register( ID_TYPE2, new concrete_factory<type2>() );
hash_table.register( ID_TYPE3, new concrete_factory<type3>() );
hash_table.register( ID_TYPE4, new concrete_factory<type4>() );

N개의 팩토리 클래스 대신, 1개의 팩토리 클래스 템플릿으로 대체가 되는 것이다!
템플릿과 함께하는 C++에 축복을! >_<


Posted by uhm
dev.log2004. 1. 30. 02:44
스테이트 머신은 복잡한 계의 행동을 모델링하는 간단한 기법으로 널리 애용되곤 한다. 스테이트 머신에 대한 자세한 설명은 여기서는 하지 않겠지만, 상태에 따라 외부의 자극에 다르게 반응하는 계를 모델링하는 기법이라고 설명하면 될듯 하다. 스테이트 머신에는 여러 종류가 있지만, 여기서는 가장 간단한 Deterministic Finite State Automata만을 고려하기로 한다.
오늘 누군가가 스테이트 머신 클래스를 만든 코드라고 한 것을 본 적이 있다. 대략적으로 다음과 같은 형상이었다.
void state_machine::handler()
{
    int state = INITIAL_STATE;
    while ( !is_final( state ) )
    {
        int s = fetch_stimuli();
        switch ( state )
        {
        case STATE0:
            state = on_state0( s );
            break;
        case STATE1:
            state = on_state1( s );
            break;
        // 다른 스테이트를 처리하는 케이스
        // ....
        default:
            state = INVALID_STATE;
        }
    }
}
int state_machine::on_state0( int s )
{
    switch ( s )
    {
    case STIMULI0:
        // 스테이트0에서 입력0를 처리하는 코드
        // ....
        return SOME_STATE;
    case STIMULI1:
        // 스테이트1에서 입력1을 처리하는 코드
        // ....
        return ANOTHER_STATE:
    // 다른 입력을 처리하는 케이스
    // ....
    default:
        return STATE0;
    }
}


이건 클래스만 썼을 뿐이지 C코드 그대로의 설계방식이다. 전혀 객체지향적이지 않다. 문제점이 보이는가?
어떤 스테이트 머신이 있을때, 그 머신의 스테이트0와 스테이트1에서의 행동은 많든 적든 다른 점이 있기 마련이다. 객체지향의 세계에서는 다른 일을 하는 개체는 다른 클래스로 만들어야 한다. 구현하고자 하는 기능을 그냥 <클래스에 때려박는다>고 해서 객체지향이 되는 것이 아니라는 것이다.

혹시 디자인 패턴 쪽을 공부해 보지 않은 분이 있다면 객체지향 설계 방법론에서 디자인 패턴은 한번 봐둘만한 가치는 있음을 말하고 싶다. 여기에서 나타나는 것이 state pattern, 혹은 strategy pattern이다. 스테이트 패턴은 어떤 객체의 스테이트를 하나의 객체로 모델링하는 기법이다.
위 스테이트 머신을 스테이트 패턴을 써서 고친다면 다음과 같이 변할 것이다.

우선, 객체의 스테이트를 나타내는 state 추상 클래스를 선언한다.

class state
{
public:
    virtual state* handle_stimuli( int s ) = 0;
};


이제 각 스테이트를 표현하는 클래스를 선언한다.
class state0 : public state
{
protected:
    state* handle_stimuli( int s )
    {
        switch ( s )
        {
        case STIMULI0:
            return handle_stimuli0();
        case STIMULI1:
            return handle_stimuli1();
        // 다른 입력을 처리하는 케이스
        // ....
        default:
            return new state0();
        }
    }
};


이제 메인 루프에서는 선언된 추상클래스의 인터페이스만 호출하면 된다.

void state_machine::handler()
{
    state* cur_state = new initial_state();
    while ( !is_final( state ) )
    {
        int s = fetch_stimuli();
        state* new_state = cur_state->handle_stimuli( s );
        delete cur_state;
        cur_state = new_state;
    }
    delete cur_state;
}


혹 동적 메모리의 할당과 해제가 빈번히 일어나는 것이 신경쓰인다면, 각 상태 객체를 미리 스테이트 머신 객체에 등록시켜 놓고 빠르게 접근하는 방법을 생각해도 되겠다. 이런 경우에는 Prototype패턴을 응용해 보면 좋을 것이다. 혹은, placement new 연산자를 오버로딩하여 사용하는 방법을 써도 좋다.

구현상의 개선이야 그렇다 치고, 이제는 설계상의 개선을 한번 찾아보자. 위 설계에 개선의 여지가 없는가? 당연히 있다. 문제는 스티뮬리를 처리하는 handle_stimuli()메소드의 구현 방식에 있다. 여기서는 스티뮬리의 값이 따라서 객체의 행동을 선택해주는 방식을 취하고 있다. 그렇다면 행동을 취하는 정보는 state객체에 있는 것이 아니라 스티뮬리에 있는 것이 된다. 그렇다면 행동의 선택은 이미 스티뮬리에 따라 주어진 것인데, 구태여 state객체가 스티뮬리에 따르는 행동의 선택에 관여할 필요는 없다는 결론이 나온다.
즉, 스테이트 객체가 스티뮬리를 처리하는 방식은 스티뮬리에 따라 결정되므로, 스티뮬리가 자기 자신을 처리하는 방식을 알고 있는 것이 합리적인 설계이다. 즉, 스티뮬리를 객체로 모델링하는 것이 정답이다. 여기에는 Command패턴을 쓰는 것이 적절하다.

우선 스티뮬리를 나타낼 추상클래스 stimuli를 선언한다.
class stimuli
{
public:
    virtual state* handle( state* s ) = 0;
};


이제 각 스티뮬리를 표현하는 클래스를 작성한다.
class stimuli0 : public stimuli
{
protected:
    state* handle( state* s )
    {
        return s->handle_stimuli0();
    }
};


그럼 이제 state객체의 선언이 바뀌게 된다.

class state0 : public state
{
protected:
    state* handle_stimuli( stimuli& s )
    {
        return s.handle( this );
    }
};


그러면 메인루프는 다음과 같아진다.

void state_machine::handler()
{
    state* cur_state = new initial_state();
    while ( !is_final( state ) )
    {
        stimuli& s = fetch_stimuli();
        state* new_state = cur_state->handle_stimuli(s);
        delete cur_state;
        cur_state = new_state;
    }
    delete cur_state;
}

처음의 코드와 가장 나중의 코드를 비교해 보면, 어느 쪽이 더 깔끔한지 판단해 보시기 바란다.

Posted by uhm