2010-01-22 3 views
2

메시지를 처리기로 라우트하는 클래스가 있다고합니다. 이 클래스는 소켓을 통해 메시지를받는 다른 클래스의 메시지를 가져옵니다. 그래서 소켓은 어떤 종류의 메시지를 담고있는 버퍼를 얻는다.알 수없는 상속 된 형식 비 직렬화 [C++]

메시지를 라우트하는 클래스는 메시지 유형을 알고 있습니다. 모든 메시지는 메시지 ID를 포함하는 Message 클래스를 상속하며, 물론 자신의 매개 변수를 추가합니다.

문제는 어떻게 버퍼에서 메시지를 올바른 유형의 실제 메시지 인스턴스로 전송할 수 있습니까?

예를 들어, 메시지를 계승하는 DoSomethingMessage가 있습니다. 나는 메시지를 담고있는 버퍼를 얻었지만, DoSomethingMessage라는 것을 알지 못하고 버퍼를 다시 DoSomethingMessage로 변환해야한다.

버퍼를 MessageRouter로 전송할 수 있었고 ID로 확인하여 올바른 인스턴스를 만들 수 있었지만 실제로는 나쁘다고 생각합니다.

제안 사항?

+0

어떤 유형의 메시지가 매핑되는지 알고 있어야하므로 일부 ID와 일부 종류의 팩토리가 필요합니다. C++은 어떤 유형으로 매핑되는지 마술처럼 추측 할 수 없습니다. –

답변

0

메시지를 역 직렬화 할 수 있습니다. 처음에는 객체에 대한 버퍼를 가진 "MessageHolder"클래스를 가져야합니다. 메서드가 있어야합니다.

IMessageInterface NarrowToInterface (MessageId id);

라우터가 이미 어떤 유형의 메시지인지 알지 못했습니다. 그럴 경우 메시지 소유자 인스턴스를 받고 NarrowToInterface 메서드를 호출합니다.

적절한 유형의 ID를 전달합니다. 라우터가 어떤 유형인지 알지 못하는 경우 MessageHolder 객체에 속성이 있습니다.

MessageId GetMessageType();

라우터가 어떤 메시지 유형을 라우트 할 것인지를 결정하는 데 사용할 것입니다. 나중에 구현되는 방법에 대해 자세히 설명합니다.

IMessageInterface는 메시지의 수신자가 예상 유형을 알 수 있으므로 적절한 유형으로 다운 캐스팅 할 추상 클래스 또는 인터페이스입니다. 다른 메시지가 모두 잘 알려져 있고 사용할 수있는 제네릭이나 템플릿이있는 경우 NarrowToInterface 메서드를 반환 값을 템플릿 매개 변수로 사용하는 템플릿 메서드로 사용할 수 있으므로 형식 안전성이 향상됩니다. 템플릿이 없으면 "Vistor"패턴의 이중 디스패치 기술을 사용할 수 있습니다. 자세한 내용은 Google의 '이중 방문 방문자'를 참조하십시오.

메시지 유형이 잘 정의되지 않았거나 앞으로 커질 수있는 경우에는 언젠가는 (컴파일러가 확인할 수없는) 다운 캐스트로 살아야합니다. 필자가 제안한 구현은 가능한 한 많이 캡슐화하고 내가 아는 한 절대적인 최소값으로 결합을 제한합니다.

또한이 기능을 사용하려면 메일에 헤더의 표준 식별자가 있어야합니다. 즉, 메시지 유형의 ID뿐만 아니라 전체 메시지의 길이를 갖는 표준 헤더가 존재한다. 그런 식으로 소켓 종단점은 메시지의 기초를 파싱하여 메시지 홀더에 넣을 수 있습니다. MessageHolder는 NarrowToInterface() 메소드를 구현하기 위해 모든 다른 메시지 유형 자체를 알거나 각 메시지 유형에 대해 NarrowToInterface를 구현하는 "IMessageDeserializer"객체를 반환하는 글로벌 저장소가있을 수 있습니다. 로드 된 모든 메시지 클라이언트는 지원하는 모든 메시지의 디시리얼라이저를 저장소에 등록하고 원하는 메시지 유형 ID를 메시지 라우터에 등록합니다.

0

소켓을 통해 메시지를 전달하는 경우 전달할 메시지의 종류를 식별 할 수있는 태그를 전달해야합니다. 이 방법으로 소켓에서 데이터를 읽을 때 어떤 유형의 객체를 생성해야하는지 알 수 있습니다. 코드는 어떤 종류의 메시지를 만들어야하는지 알고 있어야합니다. 소켓에서 온 바이너리 블롭은 그것이 무엇인지에 대한 어떠한 정보도 가지고 있지 않다.

+0

물론, 고정 된 위치에있는 메시지 자체에는 ID 필드가 있습니다. 그런 식으로 나는 그 메시지의 유형을 안다. 유일한 질문은, 언제 어디서 어떻게 만들지, 좋은 디자인 관행을 염두에 두는 것입니다. – Rob

0

처음에 데이터가 나타내고 자하는 것을 모르는 경우 어떻게 데이터를 논리적 표현으로 변환 할 수 있습니까? 제가 당신에게 0x2FD483EB을 보내면, 제가 표현하고자하는 것을 알지 못한다면 그것이 의미하는 바를 알 수있는 방법이 없습니다. 아마 하나의 32 비트 숫자 일 것입니다. 아마도 16 비트 숫자의 한쌍 일 것입니다. 아마도 4 8의 문자열 일 것입니다. 비트 문자.

소켓에서 원시 데이터를 얻었으므로 다형성에 사용되는 컴파일러 마술에 의존 할 수 없습니다. 당신이 할 수있는 일은 ID를 읽고 좋은 old switch을 사용하여 적절한 클래스를 만드는 것입니다. 물론 아이디 클래스를 자신의 ID를 인식하는 책임을지는 멋진 객체 지향 레이어와 적절한 클래스를 만들기위한 팩토리 클래스로 래핑 할 수 있습니다.

0

이미 언급했듯이 ID에서 해당 유형으로 매핑해야합니다.

관리 가능한 메시지 유형의 경우, 이진 메시지에 저장된 ID (예 : switch(messageId))를 사용하여 올바른 메시지 인스턴스를 만드는 중앙 팩토리를 사용할 수 있습니다.

거대한 팩토리 메소드의 중앙 집중화에 대해 걱정할 필요가 있습니다. 즉, 메일 수가 커지면됩니다. 클래스를 어떻게 든 등록해야하는 것을 분산화하려는 경우, 예를 들어를 참조하십시오. 기본 아이디어는 this answer입니다.
중앙 저장소로 서브 클래스의 팩토리를 등록하려면이 방법을 사용하십시오. 예 :

// common code: 

struct MessageBase { 
    virtual ~MessageBase() {} 
}; 

typedef MessageBase* (*MessageConstructor)(char* data); 

struct MessageRegistrar { 
    static std::map<unsigned, MessageConstructor> messages; 
    MessageRegistrar(unsigned id, MessageConstructor f) { 
     messages[id] = f; 
    } 
    static MessageBase* construct(unsigned id, char* data) { 
     return messages[id](data); 
    } 
}; 

#define REGISTER_MESSAGE(id, f) static MessageRegistrar registration_##id(id, f); 

// implementing a new message: 

struct ConcreteMessage : MessageBase { 
    ConcreteMessage(char* data) {} 
    static MessageBase* construct(char* data) { 
     return new ConcreteMessage(data); 
    } 
}; 

REGISTER_MESSAGE(MessageId_Concrete, &ConcreteMessage::construct); 

// constructing instances from incoming messages: 

void onIncomingData(char* buffer) { 
    unsigned id = getIdFromBuffer(buffer); 
    MessageBase* msg = MessageRestristrar::construct(id, buffer); 
    // ... 
}