6

को तत्काल करने के लिए सही उप-वर्ग का चयन करना ठीक है, संदर्भ कुछ क्रमिकरण/deserialization कोड है जो एक बाइट स्ट्रीम को 'ऑब्जेक्ट' प्रतिनिधित्व में पार्स करेगा जो (और इसके विपरीत) के साथ काम करना आसान है।प्रोग्रामेटिक रूप से

यहाँ एक आधार संदेश वर्ग के साथ एक सरल उदाहरण है और फिर एक 'प्रकार' शीर्षक पर निर्भर करता है, कुछ और डेटा/समारोह मौजूद हैं और हम का दृष्टांत का अधिकार उपवर्ग का चयन करना होगा:

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); 
}; 

कोई वास्तविक में उदाहरण, सैकड़ों अलग-अलग संदेश प्रकार और संभवतः कई स्तर या पदानुक्रम होंगे क्योंकि कुछ संदेश एक-दूसरे के साथ फ़ील्ड/फ़ंक्शंस साझा करते हैं।

अब, एक बाइट स्ट्रिंग पार्स करने में, मैं की तरह कुछ कर रहा हूँ:

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); 

लेकिन मैं इस विशाल स्विच बहुत ही सुंदर नहीं मिल रहा है, और मैं जानकारी के बारे में जो संदेश जो 'प्रकार है है मूल्य 'दो बार (एक बार कन्स्ट्रक्टर में, एक बार इस स्विच में) यह भी काफी लंबा है ...

मैं एक बेहतर तरीका ढूंढ रहा हूं जो बेहतर होगा ... क्या किसी को पता है कि कैसे सुधार करना है इस ?

उत्तर

4

यह वास्तव में एक बहुत बुनियादी सवाल है (जैसा कि आप कल्पना कर सकते है, तो आप निश्चित रूप से कर रहे हैं सी ++ में केवल एक deserializing नहीं)।

जो आप खोज रहे हैं उसे वर्चुअल कंस्ट्रक्शन कहा जाता है।

सी ++ वर्चुअल कंस्ट्रक्शन को परिभाषित नहीं करता है, लेकिन Prototype डिज़ाइन पैटर्न का उपयोग करके या Factory विधि का उपयोग करके इसे अनुमानित करना आसान है।

मैं व्यक्तिगत रूप से Factory दृष्टिकोण पसंद करता हूं, क्योंकि Prototype का मतलब है कि किसी प्रकार का डिफ़ॉल्ट उदाहरण दोहराया गया है और फिर परिभाषित किया गया है ... समस्या यह है कि सभी वर्गों का अर्थपूर्ण डिफ़ॉल्ट नहीं है, और उस मामले के लिए , एक सार्थक Default Constructor

Factory दृष्टिकोण काफी आसान है।

  • आप संदेश के लिए एक आम आधार वर्ग की जरूरत है, और Parsers के लिए एक और
  • प्रत्येक संदेश दोनों एक टैग और एक संबद्ध पार्सर

चलो कुछ कोड देखते हैं गया है:

// 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; 
}; 

और इस ढांचे के साथ (स्वीकार्य रूप से सरल), कुछ व्युत्पन्न कक्षाएं:

class MessageA: public Message 
{ 
public: 
    MessageA(int a, int b); 
}; 

class ParserA: public Parser 
{ 
public: 
    typedef std::auto_ptr<MessageA> result_type; 
    virtual result_type parse(std::istream& serialized) const 
    { 
    int a = 0, b = 0; 
    char space = 0; 
    std::istream >> a >> space >> b; 
    // Need some error control there 
    return result_type(new MessageA(a,b)); 
    } 
}; 

और अंत में, उपयोग:

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) 
} 

यह काम करता है, मुझे लगता है मैं यह (या एक बदलाव) का उपयोग करने के लिए पता है।

कुछ तथ्यों पर गौर करने के लिए कर रहे हैं:

  • आप MessageFactory एक सिंगलटन बनाने के लिए तैयार हो सकता है, तो अनुमति देता है यह पुस्तकालय लोड पर कहा जाता था, और इस प्रकार आप स्थैतिक चर instantiating द्वारा अपने पारसर्स रजिस्टर कर सकते हैं। यदि आप main नहीं चाहते हैं तो यह बहुत आसान है यदि प्रत्येक पार्सर प्रकार को पंजीकृत करना है: इलाके> कम निर्भरता।
  • टैग को साझा करना होगा। संदेश वर्ग (टैग कहा जाता है) की वर्चुअल विधि द्वारा टैग को सेवा के लिए असामान्य नहीं है।

तरह:

class Message 
{ 
public: 
    virtual ~Message(); 
    virtual const std::string& tag() const = 0; 
    virtual void serialize(std::ostream& out) const; 
}; 
  • क्रमबद्धता के लिए तर्क भी साझा करने के लिए है, यह असामान्य नहीं है एक वस्तु का अपना क्रमबद्धता/अक्रमांकन संभाल करने के लिए है

तरह:

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)); 
    } 
}; 

टी के साथ क्या बढ़िया है एम्पलेट्स यह है कि यह मुझे आश्चर्यचकित करने के लिए कभी नहीं रोकता है

class MessageFactory 
{ 
public: 
    template <class M> 
    void register() 
    { 
    m_parsers[M::Tag()] = new ParserTemplate<M>(); 
    } 
}; 

//skipping to registration 
    factory.register<MessageA>(); 

अब यह सुंदर नहीं है :)?

10

इसे देखने का एक तरीका नक्शे का उपयोग करेगा और प्रत्येक संदेश प्रकार के लिए किसी प्रकार का फ़ैक्टरी फ़ंक्शन पंजीकृत करेगा। इसका मतलब है कि आप स्विच केस से छुटकारा पा सकते हैं और संदेशों को गतिशील रूप से जोड़ और निकाल सकते हैं।

कोड कुछ इस प्रकार दिखाई तरह होगा:

// 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 वर्ग कुछ इस तरह दिखेगा:

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

वास्तव में बहुत ही रोचक दृष्टिकोण। रनटाइम बदलाव पहलू मेरे विशिष्ट मामले में वास्तव में उपयोगी नहीं है लेकिन यह एक अच्छा बोनस है जो कुछ अन्य मामलों में उपयोगी साबित हो सकता है। मैं थोड़ी देर के लिए इंतजार करूँगा अगर कोई और उत्तर देने की पुष्टि करने से पहले है। – 246tNt

+0

जाहिर है आप रनटाइम पर संदेश प्रकार नहीं जोड़ सकते हैं। वैसे भी, स्विच केस को मानचित्र में प्रति संदेश प्रकार पंजीकरण प्रविष्टि में एक पंक्ति में बदल दिया गया है, यहां बड़ी जीत नहीं दिखाई दे रही है, निश्चित रूप से परिणामी कोड की कोई कम मात्रा नहीं, और अधिक कुशल? शायद कम अव्यवस्था? –

+0

मैं बहुत चिंतित नहीं हूं, मैं प्रति सेकंड सैकड़ों हजारों संदेशों को संसाधित नहीं कर रहा हूं, कोड शैली यहां अधिक चिंता का विषय है। यह भी जीत मैं यहां देखता हूं (भले ही प्रश्न से स्पष्ट न हो), यह है कि अगर मैं इस तरह की अन्य चीज़ की ज़रूरत है, तो मैं 'createMessage' की तुलना में अन्य विधियों को भी जोड़ सकता हूं, जैसे परिभाषित करना कि 'नियंत्रण वर्ग' कौन सा संभाल लेगा संदेश और सामान। रनटाइम बदलाव के लिए: आप रनटाइम पर मानचित्र को संशोधित कर सकते हैं। नए संदेश जोड़ने के लिए आप उन्हें साझा वस्तुओं जैसे प्लगइन्स और इस तरह से डाउनलोड कर सकते हैं। – 246tNt