2009-11-13 12 views
6

Tamam, bu bağlamda, bir bayt akışını, çalışması daha kolay olan bir 'nesne' temsiline ayıracak bazı serileştirme/serileştirme kodudur (ve tam tersi).Programatik olarak başlatmak için doğru alt sınıfı seçme

Burada taban mesajı sınıf ile basitleştirilmiş bir örnek ve daha sonra bir 'türünün' başlığının bağlı olarak bazı daha fazla veri/fonksiyon mevcut ve biz örneğini sağ alt sınıfını seçmelisiniz:

class BaseMessage { 
public: 
    enum Type { 
     MyMessageA = 0x5a, 
     MyMessageB = 0xa5, 
    }; 

    BaseMessage(Type type) : mType(type) { } 
    virtual ~BaseMessage() { } 

    Type type() const { return mType; } 

protected: 
    Type mType; 

    virtual void parse(void *data, size_t len); 
}; 

class MyMessageA { 
public: 
    MyMessageA() : BaseMessage(MyMessageA) { } 

    /* message A specific stuf ... */ 

protected: 
    virtual void parse(void *data, size_t len); 
}; 

class MyMessageB { 
public: 
    MyMessageB() : BaseMessage(MyMessageB) { } 

    /* message B specific stuf ... */ 

protected: 
    virtual void parse(void *data, size_t len); 
}; 

gerçek olarak Bazı mesajlar birbirleriyle alanları/fonksiyonları paylaştığı için yüzlerce farklı mesaj türü ve muhtemelen birkaç seviye veya hiyerarşi olacaktır. Şimdi

, bir bayt dizesini ayrıştırmak için, şöyle bir şey yapıyorum:

BaseMessage *msg = NULL; 
Type type = (Type)data[0]; 

switch (type) { 
    case MyMessageA: 
     msg = new MyMessageA(); 
     break; 

    case MyMessageB: 
     msg = new MyMessageB(); 
     break; 

    default: 
     /* protocol error */ 
} 

if (msg) 
    msg->parse(data, len); 

Ama bu büyük anahtarı çok şık bulmuyorum, ben mesajı hangi 'türü olan hakkında bilgi sahibi değer 'iki kez (bir kez bu kurucuda bir, bu geçişte bir tane) Ayrıca oldukça uzun bir ...

Sadece daha iyi olurdu daha iyi bir yol arıyorum ... Herkes nasıl bir fikir geliştirmek için var mı bu ?

cevap

4

Aslında oldukça basit bir soru tahmin edebileceğin gibi (bu, kesinlikle vardır C++ 'da serileştiren tek değil.

Aradığın şey Sanal Yapı olarak adlandırılır.

C++, Sanal Yapısını tanımlamaz, ancak Prototype Tasarım Kalıbı'nı kullanarak veya Factory yöntemini kullanarak yaklaşık olarak tahmin etmek kolaydır.

Ben bugün Prototype biri çoğaltılmış ve SONRA tanımlanan varsayılan örneği çeşit sahip olmak demektir bu nedenle, Factory yaklaşım tercih

... sorun değil bütün sınıflar anlamlı varsayılan var ve bu olduğunu bu konuda anlamlı bir Default Constructor.

Factory yaklaşımı yeterince kolaydır.

  • Sen Mesajları için ortak bir temel sınıf gerekir ve ayrıştırıcıları başka
  • Her Message Etiket ve ilişkili bir Ayrıştırıcı hem

Biraz kod görelim etti:

// Framework 
class Message 
{ 
public: 
    virtual ~Message(); 
}; 

class Parser 
{ 
public: 
    virtual ~Parser(); 
    virtual std::auto_ptr<Message> parse(std::istream& serialized) const; 
}; 

// Factory of Messages 
class MessageFactory 
{ 
public: 
    void register(std::string const& tag, Parser const& parser); 
    std::auto_ptr<Message> build(std::string const& tag, std::istream& serialized) const; 
private: 
    std::map<std::string,Parser const*> m_parsers; 
}; 

Ve bu çerçeveyle (kuşkusuz basit), bazı türetilmiş sınıflar:

Ve sonundakullanın: Bunu (veya bir varyasyon) kullanan için Çalışıyor

int main(int argc, char* argv[]) 
{ 
    // Register the parsers 
    MessageFactory factory; 
    factory.register("A", ParserA()); 

    // take a file 
    // which contains 'A 1 2\n' 
    std::ifstream file = std::ifstream("file.txt"); 
    std::string tag; 
    file >> tag; 
    std::auto_ptr<Message> message = factory.parse(tag, file); 

    // message now points to an instance of MessageA built by MessageA(1,2) 
} 

, biliyorum.

bazı şeyler düşünmeye vardır:

  • Sen MessageFactory a tek yapmaya istekli olabilir, bu o zaman kütüphane yükte çağrılmasına olanak veren ve böylece statik değişkenler başlatmasını ederek ayrıştırıcıları kaydedebilirsiniz. main'un her bir ayrıştırıcı türünü kaydettirmesini istemiyorsanız bu çok kullanışlıdır: konumluluk> daha az bağımlılık.
  • Etiketler paylaşılmalı. İletinin, Message sınıfının sanal bir yöntemiyle sunulması (etiket adı verilir) alışılmadık değildir.

gibi: Bir nesne, kendi seri hale/deserialization işlemek için

class Message 
{ 
public: 
    virtual ~Message(); 
    virtual const std::string& tag() const = 0; 
    virtual void serialize(std::ostream& out) const; 
}; 
    serileştirme için mantık çok paylaşılan zorundadır
  • , anormal değildir

gibi:

class MessageA: public Message 
{ 
public: 
    static const std::string& Tag(); 
    virtual const std::string& tag() const; 
    virtual void serialize(std::ostream& out) const; 

    MessageA(std::istream& in); 
}; 

template <class M> 
class ParserTemplate: public Parser // not really a parser now... 
{ 
public: 
    virtual std::auto_ptr<M> parse(std::istream& in) const 
    { 
    return std::auto_ptr<M>(new M(in)); 
    } 
}; 

Şununla ne harika bununla ilgili hiçbir şey beni şaşırtmayacağıdır.

10

Bir yaklaşma yolu, bir harita kullanmak ve her mesaj tipi için bir çeşit fabrika işlevini kaydetmek olacaktır. Bu, anahtar kutusundan kurtulmanız ve iletileri dinamik olarak ekleyip kaldırabileceğiniz anlamına gelir.

// Create the map (most likely a member in a different class) 
std::map<BaseMessage::Type, MessageCreator*> messageMap; 
... 

// Register some message types 
// Note that you can add and remove messages at runtime here 
messageMap[BaseMessage::MyMessageA] = new MessageCreatorT<BaseMessageA>(); 
messageMap[BaseMessage::MyMessageB] = new MessageCreatorT<BaseMessageB>(); 
... 

// Handle a message 
std::map<Type, MessageCreator*>::const_iterator it = messageMap.find(msgType); 
if(it == messageMap.end()) { 
    // Unknown message type 
    beepHang(); 
} 
// Now create the message 
BaseMessage* msg = it->second.createMessage(data); 

MessageCreator sınıfı böyle bir şey görünecektir:: gibi

kod görünecektir

class MessageCreator { 
    public: 
    virtual BaseMessage* createMessage(void* data, size_t len) const = 0; 
}; 
template<class T> class MessageCreatorT : public MessageCreator { 
    public: 
    BaseMessage* createMessage(void* data, size_t len) const { 
     T* newMessage = new T(); 
     newMessage.parse(data, len); 
     return newMessage; 
    } 
}; 
+0

Gerçekten çok ilginç bir yaklaşım. Çalışma zamanı değişikliği özelliği, özel durumumda gerçekten yararlı değil, ancak diğer bazı durumlarda yararlı olabileceğini gösteren hoş bir bonus. Yanıt olarak onaylanmadan önce başka birinin olup olmadığını biraz bekleyeceğim. – 246tNt

+0

Açıkçası, çalışma zamanında mesaj türleri ekleyemezsiniz. Her neyse, anahtar durumu haritaya kayıt tipi kaydı girişi başına bir satıra dönüştürüldü, burada büyük kazanmayı görmüyoruz, sonuçta ortaya çıkan kod daha az miktarda, daha verimli mi? Muhtemelen daha az dağınıklık? –

+0

Performans açısından çok endişelenmiyorum, saniyede yüz binlerce iletiyi işleyemiyorum, kod stili burada daha fazla endişe kaynağı. Ayrıca burada gördüğüm (sorudan açıkça anlaşılmasa bile) kazan, 'kontrol sınıfı' tanımlamak gibi, sıralamadaki başka bir şeye ihtiyacım varsa, 'createMessage' den başka yöntemler de ekleyebilirim. mesaj ve şeyler. Çalışma zamanı değişikliği için: Haritayı çalışma zamanında değiştirebilirsiniz. Yeni mesajlar eklemek için bunları eklentiler ve benzeri gibi paylaşılan nesnelerden indirebilirsiniz. – 246tNt