2011-02-10 3 views
2

나는 상태 머신을 구현하는 좋은 방법이 싱글 톤 패턴을 사용하는 것이라고 생각한다. 예를 들어, 그것은 다음과 같이 할 수 있습니다C++의 상태 머신을 통해 singleton?

class A 
{ 

private: 
    friend class State; 
    State* _state; 
    void change_state(State* state) { _state = state; } 
}; 

class State 
{ 
    virtual void action(A *a) = 0; 
private: 
    void change_state(A *a, State *state) { a->change_state(state); } 
}; 

class StateA : public State 
{ 
public: 
    static State* get_instance() 
    { 
     static State *state = new StateA; 
     return state; 
    } 
    virtual void action(A *a) { change_state(a, StateB::get_instance(); } 
}; 

class StateB : public State 
{ 
public: 
    ... 
    virtual void action(A *a) { change_state(a, StateA::get_instance(); } 
}; 

내 질문은 : 나는 싱글 톤 패턴이 너무 악한 것을에 대해 많은 기사를 읽었습니다. 싱글 톤 패턴없이 이것을 구현하면 상태를 변경할 때마다 새로운 것을 호출해야하기 때문에 싱글 톤을 좋아하지 않는 사람들은 상태 머신 패턴을 어떻게 구현할 것인가?

답변

2

StateA, StateB 클래스에는 데이터 멤버가 없습니다. 아마도 다른 주에서는 수정할 수있는 데이터 멤버가 없을 것입니다. 그럴 경우 해당 상태가 A의 서로 다른 인스턴스간에 이상하게 공유되므로 서로 다른 상태 시스템이 동시에 실행됩니다.

그래서 싱글 톤은 패턴의 문제 (전역 변경 가능 상태)의 절반을 피했습니다. 실제로 디자인을 약간만 변경하면 상태 클래스를 함수로 대체 할 수 있습니다. 함수 포인터로 인스턴스에 대한 포인터를 대체합니다. 가상 호출을 action으로 바꾸고 현재 함수 포인터를 통해 호출합니다. 누군가가 싱글 톤을 사용하는 데 많은 번거 로움을 줄지 만 자신의 디자인이 정확하다고 확신하면이 사소한 변화를 만들어 "수정"이 디자인에 전혀 큰 변화를주지 않았 음을 알 수 있습니다.

싱글 톤 문제의 나머지 절반은 여전히 ​​해결되지 않았으며 고정 종속성입니다.싱글 톤을 사용하면 StateA를 독립적으로 테스트하거나 현재 상태와 동일한 새 상태 머신을 라이브러리에 추가하려는 경우 유연성을 도입하기 위해 StateB를 모방 할 수 없습니다 of StateB. 당신은 그 문제를 고려할 수도 있고 고려하지 않을 수도 있습니다. 그렇게한다면, 각 주를 싱글 톤으로 만드는 것보다 일을 더 구성 가능하게 만들어야합니다.

예를 들어, 각 상태에 식별자 (문자열 또는 열거 형 멤버)를 줄 수 있고 각 식별자에 대해 A 클래스의 어딘가에 State*을 등록 할 수 있습니다. 그런 다음 StateB, StateA의 singleton 인스턴스로 넘기지 않고 이 상태 기계의 "상태 B"을 나타내는 데 사용되는 모든 상태 개체로 전환 할 수 있습니다.. 그러면 특정 인스턴스에 대한 테스트 모의가 될 수 있습니다. 시스템 당 한 번 상태 당 new으로 전화를 걸지 만 상태 변경 당 한 번은 전화하지 않습니다.

실제로 이것은 디자인에서와 마찬가지로 클래스 A의 전략 패턴입니다. 그러나 상태 시스템을 앞으로 이동시키는 단일 전략을 가지지 않고 상태가 변경됨에 따라 지속적으로이를 대체하는 것보다는 시스템이 통과하는 상태마다 동일한 인터페이스를 사용하는 하나의 전략이 있습니다. C++의 또 다른 옵션은 전략 대신 정책 기반 디자인 (형태)을 사용하는 것입니다. 그런 다음 각 상태는 객체 (런타임에 설정)보다는 클래스 (템플릿 인수로 제공됨)에 의해 처리됩니다. 따라서 상태 머신의 동작은 컴파일시 (현재 디자인에서와 같이) 고정되지만 StateB 클래스를 변경하거나 대체하지 않고 템플릿 인수를 변경하여 구성 할 수 있습니다. 그런 다음 new을 호출 할 필요가 없습니다. 상태 시스템에서 각 상태의 단일 인스턴스를 데이터 멤버로 만들고 그 중 하나에 대한 포인터를 사용하여 현재 상태를 나타내며 가상으로 호출합니다. 전에. 정책 기반 설계는 일반적으로 가상 호출을 필요로하지 않습니다. 일반적으로 별도의 정책은 완전히 독립적이지만, 여기서는 공통 인터페이스를 구현하고 런타임에 이들 사이에서 선택하기 때문입니다.

이 모든 것은 A가 유한 상태 집합을 알고 있다고 가정합니다. 이것은 현실적이지 않을 수 있습니다 (예를 들어, A는 임의의 수의 임의 상태를 허용해야하는 다목적 프로그래머블 상태 시스템을 나타낼 수 있습니다). 이 경우 상태를 구축 할 방법이 필요합니다. 먼저 StateA 인스턴스와 StateB 인스턴스를 만듭니다. 각 상태에는 하나의 이탈 경로가 있으므로 각 상태 객체에는 새 상태에 대한 포인터 인 하나의 데이터 멤버가 있어야합니다. 따라서 상태를 생성 한 후 StateA 인스턴스를 StateB 인스턴스에 "다음 상태"로 설정하거나 StateB 인스턴스에 StateB 인스턴스를 "다음 상태"로 설정합니다. 마지막으로 A의 현재 상태 데이터 멤버를 StateA의 인스턴스로 설정하고 실행을 시작합니다. 이렇게하면 종속성의 순환 그래프를 작성하므로 메모리 누수를 방지하기 위해 참조 계산 이외의 특별한 리소스 처리 방법을 사용해야 할 수도 있습니다.

4

여기서는 싱글 톤 패턴이 적절하지 않다고 생각합니다. 싱글 톤은 실제로 하나의 복사본 만있는 추상 개체 또는 실제 개체를 나타내는 데 적합합니다. Java에서 예제를 훔치려면 프로그램의 특정 인스턴스가 실행되는 런타임 환경이 하나만 있습니다. 싱글 톤은 캡슐화를 유지하면서 다중 가능한 백엔드를 허용하면서 전체 프로그램에 이름 지정 및 참조 기능을 부여하기 때문에 이러한 객체를 나타내는 데 적합합니다.

이 점을 감안할 때, 나는 싱글 톤이 당신의 상태 머신을 위해 취할 수있는 최상의 경로라고 동의하지 않습니다. 만약 싱글 톤으로 구현한다면, 항상 그 상태 머신의 복사본 하나라고 말하고있는 것입니다. 하지만 두 개의 상태 머신을 병렬로 실행하려면 어떻게해야합니까? 또는 상태 시스템이 전혀 없습니까? 자기 자신의 로컬 상태 머신이 어떤 결과를 겪을 지 시험해 볼 수 있다면 어떨까요? 상태 머신이 싱글 톤이라면 전체 프로그램에서 실제로 사용되는 상태 시스템이 하나뿐이기 때문에 이러한 작업을 수행 할 수 없습니다.

이제 상태 시스템을 사용하는 방법에 따라 적절할 수도 있습니다. 상태 머신이 프로그램의 전체 실행을 제어하면 좋은 생각 일 수 있습니다. 예를 들어 비디오 게임을 개발 중이고 상태 머신이 메뉴 나 채팅 영역, 게임 중 어느 것을 제어하길 원한다면 싱글 톤 상태 머신을 사용하는 것이 좋습니다. 언제든지 프로그램의 논리 상태는 하나뿐입니다. 귀하의 질문에서, 그렇다고해도 나는 추론 할 수 없습니다.

싱글 톤없이 상태 머신을 구현하는 방법은 상태 머신 객체가 모든 상태의 자체 복사본을 할당하고 (명시 적 상태 객체가 필요한 경우) 천이 테이블을 구축하거나, 자이언트 스위치 문과 현재 상태를 제어하는 ​​단일 열거 형 값입니다. 상태 시스템의 단일 인스턴스가있는 경우 현재 버전보다 효율적이지 않으며 여러 인스턴스가있는 경우 로컬 정보를 저장할 수 있습니다 각 주에서 프로그램의 다른 부분에서 읽을 수있는 상태의 전역 사본을 오염시키지 않습니다.

+1

"두 개의 상태 머신을 병렬로 실행하려면 어떻게해야합니까?" 상태 머신은 싱글 톤이 아닙니다. * 상태 *는 싱글 톤입니다. 그러나 위의 디자인에서 상태 머신은 A입니다. 우리는 원하는만큼 인스턴스를 생성 할 수 있습니다. –

+1

@Steve Jessop- 내 관심사는 상태 머신 상태가 일종의 상태를 필요로하는 싱글 톤 (예를 들어, 네트워크 프로토콜의 컨트롤 인 경우 상태는 IP 주소 등을 저장해야 할 수도 있음) 인 경우 상태가 싱글 톤 (singleton)으로되어있어 서로를 파괴적으로 간섭하지 않고 여러 상태 시스템을 가질 수 없습니다. 아마 내가 분명히 했어야했는데 ... 세부 사항을 깨닫기 전에이 점을 많이 썼다. :-) – templatetypedef

0

코드에서는 상태가 속한 상태 시스템과 상태를 연결하지 않습니다 (클래스 A가 상태 시스템이라고 가정). 이 정보는 조치 메소드로 전달됩니다. 따라서 클래스 A의 인스턴스가 두 개 (예 : 두 개의 상태 시스템) 인 경우 상태가 잘못된 상태 시스템으로 업데이트 될 수 있습니다.

새로운 기능을 반복적으로 호출하는 것을 피하기 위해이 기능을 사용하고 속도를 높이기 위해 삭제하는 경우에는 시기상조 일 수 있습니다. 더 나은 해결책은, new와 delete가 너무 느리거나 다른 문제 (예를 들어 메모리 단편화)를 일으킨다는 것을 보여 주면, 자신의 메모리 풀에서 할당하는 State 기본 클래스에 operator new/delete를 정의하는 것입니다. 한 쌍, 메모리 풀은 어떤 상태를 저장하기에 충분히 큰 각 메모리의 덩어리의 배열이 될 수

class StateMachine 
{ 
public: 
    SetState (State state) { next_state = state; } 
    ProcessMessage (Message message) 
    { 
    current_state->ProcessMessage (message); 
    if (next_state) 
    { 
     delete current_state; 
     current_state = next_state; 
     next_state = 0; 
    } 
    } 
private: 
    State current_state, next_state; 
} 

class State 
{ 
public: 
    State (StateMachine owner) { m_owner = owner; } 
    virtual ProcessMessage (Message message) = 0; 
    void *operator new (size_t size) // allocator 
    { 
    return memory from local memory pool 
    } 
    void operator delete (void *memory) // deallocator 
    { 
    put memory back into memory pool 
    } 
protected: 
    StateMachine m_owner; 
}; 

class StateA : State 
{ 
public: 
    StateA (StateMachine owner) : State (owner) {} 
    ProcessMessage (Message message) 
    { 
    m_owner->SetState (new StateB (m_owner)); 
    } 
} 

: 여기

내가 현재 작품을 사용하고 어떻게 상태 머신에 대한 몇 가지 의사의 할당 된 블록 및 할당되지 않은 블록에 대해 하나씩 나열됩니다.그러면 블록을 할당하면 할당되지 않은 목록에서 블록을 제거하고 할당 된 목록에 추가하는 프로세스가됩니다. 그런 다음 자유롭게하는 것이 그 반대의 과정입니다. 나는이 유형의 할당 전략에 대해 '자유 목록'이라는 용어를 생각합니다. 그것은 매우 빠르지 만 약간의 낭비 된 기억을 가지고 있습니다. 모든 상태 객체가 같은 수의 상태 머신 (StateMachine)을 따라 살고 있다고 가정

0

한 가지 방법이 하나 : 큐 파견에서 이벤트를 기다릴 것이다 이벤트 큐 및 이벤트 루프를 것보다 복잡한 솔루션 머신 (StateMachine)에서

enum StateID 
{ 
    STATE_A, 
    STATE_B, 
    ... 
}; 

// state changes are triggered by events 
enum EventID 
{ 
    EVENT_1, 
    EVENT_2, 
    ... 
}; 

// state manager (state machine) 
class StateMachine 
{ 
    friend StateA; 
    friend StateB; 
    ... 

public: 
    StateMachine(); 
    ~StateMachine(); 
    // state machine receives events from external environment 
    void Action(EventID eventID); 
private: 
    // current state 
    State* m_pState; 

    // all states 
    StateA* m_pStateA; 
    StateB* m_pStateB; 
    ... 

    void SetState(StateID stateID);  
}; 

StateMachine::StateMachine() 
{ 
    // create all states 
    m_pStateA = new StateA(this, STATE_A); 
    m_pStateB = new StateB(this, STATE_B); 
    ... 

    // set initial state 
    m_pState = m_pStateA; 
} 

StateMachine::~StateMachine() 
{ 
    delete m_pStateA; 
    delete m_pStateB; 
    ... 
} 

void StateMachine::SetState(StateID stateID) 
{ 
    switch(stateID) 
    { 
    case STATE_A: 
     m_pState = m_pStateA; 
     break; 
    case STATE_B: 
     m_pState = m_pStateA; 
     break; 
    ... 
    } 
} 

void StateMachine::Action(EventID eventID) 
{ 
    // received event is dispatched to current state for processing 
    m_pState->Action(eventID); 
} 

// abstract class 
class State 
{ 
public: 
    State(StateMachine* pStateMachine, StateID stateID); 
    virtual ~State(); 
    virtual void Action(EventID eventID) = 0; 
private: 
    StateMachine* m_pStateMachine; 
    StateID m_stateID;  
}; 

class StateA : public State 
{ 
public: 
    StateA(StateMachine* pStateMachine, StateID stateID);  
    void Action(EventID eventID); 
}; 

StateA::StateA(StateMachine* pStateMachine, StateID stateID) : 
    State(pStateMachine, stateID) {...} 

void StateA::Action(EventID eventID) 
{ 
    switch(eventID) 
    { 
    case EVENT_1: 
     m_pStateMachine->SetState(STATE_B); 
     break; 
    case EVENT_2: 
     m_pStateMachine->SetState(STATE_C); 
     break; 
    ... 
    } 
} 

void StateB::Action(EventID eventID) 
{ 
    switch(eventID) 
    { 
    ... 
    case EVENT_2: 
     m_pStateMachine->SetState(STATE_A); 
     break; 
    ... 
    } 
} 

int main() 
{ 
    StateMachine sm; 
    // state machine is now in STATE_A 

    sm.Action(EVENT_1); 
    // state machine is now in STATE_B 

    sm.Action(EVENT_2); 
    // state machine is now in STATE_A 

    return 0; 
} 

그것들을 현재 상태로 보냅니다. StateX::Action(...)의 시간 소모적 인 작업은 이벤트 루프를 막지 않도록 별도의 (작업자) 스레드에서 실행해야합니다.

0

내가 생각하고있는 디자인 접근법은 싱글 톤인 상태 팩토리를 만드는 것이므로 더 많은 상태 머신이 팩토리에서 생성 된 상태 객체를 사용할 수 있습니다.

하지만이 생각은 플라이급 패턴으로 내 상태 공장을 구현하려는 아이디어를 얻었습니다. 그리고 그 곳에서 멈췄습니다.

기본적으로 플라이 웨이트로 상태 개체를 구현 한 후 플라이급 디자인 패턴의 장점을 연구해야합니다.

필자는이 상태 시스템에 대해이 유형의 패턴을 사용하고 있다고 들었지만 필자의 필요에 따라 작동하는지 확신 할 수 없습니다.

어쨌든, 나는 약간의 조사를하고 있었고이 게시물에 부딪 혔습니다. 그냥 공유 할 줄 ​​알았는데 ...

+0

싱글 톤은 절대 대답이되지 않습니다 – thecoshman

+0

@thecoshman : _S_ingletons는 결코 대답이 아니며 _s_ingletons 일 수도 있습니다. – DrP3pp3r