2009-09-29 10 views
7

यह एक सामान्य सी ++ डिजाइन सवाल है।सी ++ में पारदर्शी रूप से विभिन्न प्रोटोकॉल संस्करणों को कैसे संभालें?

मैं एक ऐसा एप्लिकेशन लिख रहा हूं जो क्लाइंट/सर्वर मॉडल का उपयोग करता हो। अभी मैं सर्वर-साइड लिख रहा हूं। कई ग्राहक पहले से मौजूद हैं (कुछ स्वयं द्वारा लिखे गए हैं, अन्य तृतीय पक्षों द्वारा)। समस्या यह है कि ये मौजूदा ग्राहक विभिन्न प्रोटोकॉल संस्करणों का उपयोग करते हैं (वर्षों में 2-3 प्रोटोकॉल परिवर्तन हुए हैं)।

जब से मैं कर रहा हूँ सर्वर को फिर से लिखकर, मैंने सोचा कि यह एक महान मेरे कोड इस तरह है कि मैं कई अलग अलग प्रोटोकॉल संस्करणों पारदर्शी रूप से संभाल कर सकते हैं डिजाइन करने के लिए समय होगा। सभी प्रोटोकॉल संस्करणों में, क्लाइंट से पहले संचार में प्रोटोकॉल संस्करण होता है, इसलिए प्रत्येक क्लाइंट कनेक्शन के लिए, सर्वर जानता है कि किस प्रोटोकॉल को बात करने की आवश्यकता है।

अनुभवहीन यह करने के लिए विधि कूड़े को इस तरह बयान के साथ कोड है:

if (clientProtocolVersion == 1) 
    // do something here 
else if (clientProtocolVersion == 2) 
    // do something else here 
else if (clientProtocolVersion == 3) 
    // do a third thing here... 

यह समाधान बहुत खराब है, निम्नलिखित कारणों के लिए:

  1. जब मैं एक जोड़ने नया प्रोटोकॉल संस्करण, मुझे स्रोत पेड़ में हर जगह मिलना है कि यदि कथन का उपयोग किया जाता है, और नई कार्यक्षमता जोड़ने के लिए उन्हें संशोधित करें।
  2. एक नए प्रोटोकॉल संस्करण के साथ आता है, और प्रोटोकॉल संस्करण के कुछ भागों एक और संस्करण के रूप में ही कर रहे हैं, मैं अगर बयान संशोधित करने की जरूरत है ताकि वे if (clientProtoVersion == 5 || clientProtoVersion == 6) पढ़ें।
  3. मुझे यकीन है कि यह खराब डिज़ाइन क्यों है और अधिक कारण हैं, लेकिन मैं अभी उनके बारे में नहीं सोच सकता।

मैं जो खोज रहा हूं वह सी ++ लैंगेज की विशेषताओं का उपयोग करके समझदारी से विभिन्न प्रोटोकॉल को संभालने का एक तरीका है। मैंने टेम्पलेट कक्षाओं का उपयोग करने के बारे में सोचा है, संभवतः प्रोटोकॉल संस्करण निर्दिष्ट करने वाले टेम्पलेट पैरामीटर के साथ, या शायद एक वर्ग विरासत, प्रत्येक अलग प्रोटोकॉल संस्करण के लिए एक वर्ग ...

मुझे यकीन है कि यह एक बहुत ही सामान्य डिजाइन पैटर्न है , इतने सारे लोगों को पहले यह समस्या होनी चाहिए थी।

संपादित करें:

आप में से कई लोग एक विरासत पदानुक्रम का सुझाव दिया है, शीर्ष पर सबसे पुराना प्रोटोकॉल संस्करण के साथ, इस तरह (मेरे ASCII आर्ट के लिए क्षमा करें):

IProtocol 
    ^
    | 
CProtoVersion1 
    ^
    | 
CProtoVersion2 
    ^
    | 
CProtoVersion3 

... यह पुन: उपयोग के मामले में करने के लिए एक समझदार चीज की तरह लगता है। हालांकि, क्या होता है जब आपको प्रोटोकॉल का विस्तार करने और मौलिक रूप से नए संदेश प्रकार जोड़ने की आवश्यकता होती है? अगर मैं IProtocol में आभासी तरीके जोड़, और CProtocolVersion4 में इन नए तरीकों को लागू करने, कैसे इन नए तरीकों पहले प्रोटोकॉल संस्करणों में इलाज कर रहे हैं? मुझे लगता है कि मेरे विकल्प हैं:

  • डिफ़ॉल्ट कार्यान्वयन को NO_OP (या संभवतः किसी संदेश को लॉग ऑन करें) बनाएं। हालांकि यह एक बुरा विचार की तरह लगता है
  • , एक अपवाद फेंक के रूप में भी मैं इसे टाइप कर रहा हूँ।
  • ... कुछ और?

EDIT2:

उपरोक्त मुद्दों को

इसके अलावा, क्या होता है एक नए प्रोटोकॉल संदेश एक पुराने संस्करण की तुलना में अधिक इनपुट की आवश्यकता है जब? उदाहरण के लिए:

protocl संस्करण 1 में, मैं हो सकता है:

ByteArray getFooMessage(string param1, int param2)

और प्रोटोकॉल संस्करण 2 में मैं चाह सकते हैं:

ByteArray getFooMessage(string param1, int param2, float param3)

दो अलग अलग प्रोटोकॉल संस्करणों अब अलग-अलग विधि हस्ताक्षर, जो ठीक है, सिवाय इसके कि यह मुझे सभी कॉलिंग कोड से गुज़रने के लिए मजबूर करता है और प्रोटोकॉल संस्करण का उपयोग करने के आधार पर, 2 पैरा से 3 पैरा के साथ सभी कॉल बदलता है, जिसे मैं उपयोग करता हूं मैं पहली जगह से बचने की कोशिश कर रहा हूँ!

प्रोटोकॉल संस्करण जानकारी को आपके शेष कोड से अलग करने का सबसे अच्छा तरीका क्या है, जैसे वर्तमान प्रोटोकॉल के विनिर्देश आपके से छिपाए गए हैं?

उत्तर

10

चूंकि आपको गतिशील रूप से प्रोटोकॉल संस्करण का चयन करने के लिए विभिन्न वर्गों (टेम्पलेट पैरामीटर के बजाए) का उपयोग करने के लिए कौन से प्रोटोकॉल का उपयोग करना है, यह चुनने का सही तरीका लगता है। अनिवार्य रूप से यह रणनीति पैटर्न है, हालांकि यदि आप वास्तव में विस्तृत होना चाहते हैं तो विज़िटर भी एक संभावना होगी।

चूंकि ये एक ही प्रोटोकॉल के सभी अलग-अलग संस्करण हैं, इसलिए संभवतः आप बेस क्लास में सामान्य सामान हो सकते हैं, और फिर उप वर्गों में अंतर हो सकते हैं। एक अन्य दृष्टिकोण हो सकता है कि आधार वर्ग प्रोटोकॉल के सबसे पुराने संस्करण के लिए हो और उसके बाद प्रत्येक अनुवर्ती संस्करण में एक वर्ग हो जो पिछले संस्करण से प्राप्त हो। यह कुछ असामान्य विरासत वृक्ष है, लेकिन इसके बारे में अच्छी बात यह है कि यह गारंटी देता है कि बाद के संस्करणों के लिए किए गए परिवर्तन पुराने संस्करणों को प्रभावित नहीं करते हैं। (मुझे लगता है कि प्रोटोकॉल के पुराने संस्करणों के लिए कक्षाएं बहुत तेज़ी से स्थिर हो जाएंगी और फिर शायद ही कभी बदल जाएंगी।

हालांकि आप पदानुक्रम को व्यवस्थित करने का निर्णय लेते हैं, तो आप जैसे ही आप प्रोटोकॉल संस्करण ऑब्जेक्ट को चुनना चाहते हैं प्रोटोकॉल संस्करण को जानें, और उसके बाद उस प्रोटोकॉल को "बात" करने के लिए अपनी विभिन्न चीजों को पास करें।

+2

दरअसल रणनीति पैटर्न सबसे संभावित दृष्टिकोण है, तो आप शायद फैक्टरी के साथ गठबंधन करना चाहते हैं जो आपके द्वारा उपयोग किए जा रहे संस्करण के लिए सही 'रणनीति' प्रदान करने के लिए जिम्मेदार होगा। –

0

मैं एक ही इंटरफ़ेस के विभिन्न प्रोटोकॉल के लिए एडाप्टर को लागू करने के लिए विभिन्न वर्गों का उपयोग करने की ओर रुख करता हूं।

प्रोटोकॉल और मतभेदों के आधार पर, आपको राज्य मशीनों या प्रोटोकॉल विवरणों के लिए टीएमपी का उपयोग करके कुछ लाभ मिल सकता है, लेकिन छह प्रोटोकॉल संस्करणों का उपयोग करने वाले जो भी कोड के छह सेट उत्पन्न करना संभवतः इसके लायक नहीं है; रनटाइम बहुरूपता पर्याप्त है, और ज्यादातर मामलों के लिए टीसीपी आईओ शायद इतना धीमा है कि सब कुछ कड़ी मेहनत नहीं करना चाहता।

0

शायद अधिक विस्तारित है, लेकिन यह विरासत के लिए नौकरी की तरह लगता है? एक बेस क्लास आईप्रोटोकॉल जो प्रोटोकॉल को परिभाषित करता है (और संभवतः कुछ सामान्य तरीकों), और फिर आपके पास प्रत्येक प्रोटोकॉल के लिए आईप्रोटोकॉल के लिए एक कार्यान्वयन है?

0

आप एक प्रोटोकॉल कारखाने कि appropraite संस्करण के लिए एक प्रोटोकॉल हैंडलर रिटर्न की जरूरत है:

ProtocolHandler& handler = ProtocolFactory::getProtocolHandler(clientProtocolVersion); 

फिर आप विरासत का उपयोग कर सकते केवल प्रोटोकॉल कि संस्करणों के बीच बदल दिया गया है के कुछ हिस्सों को अद्यतन करने के।

ध्यान दें कि प्रोटोकॉल हैंडलर (बेस क्लास) मूल रूप से एक स्ट्रैटजी पैटर्न है। इस तरह यह अपने राज्य की स्थिति नहीं रखनी चाहिए (यदि आवश्यक हो तो विधियों के माध्यम से पारित किया गया हो और स्ट्रेटर्जी राज्य वस्तु की संपत्ति को अपडेट करेगी)।

स्ट्रेटजी बनने से राज्य को बनाए रखा नहीं जाता है हम प्रोटोकॉल हैंडलर को किसी भी थ्रेड के बीच साझा कर सकते हैं और इस तरह के स्वामित्व को फैक्ट्री ऑब्जेक्ट छोड़ने की आवश्यकता नहीं है। इस प्रकार फैक्ट्री को केवल प्रत्येक प्रोटोकॉल संस्करण के लिए एक हैंडलर ऑब्जेक्ट बनाने की आवश्यकता होती है जिसे यह समझता है (यह आलसी भी किया जा सकता है)। फैक्ट्री ऑब्जेक्ट बनना स्वामित्व बरकरार रखता है आप प्रोटोकॉल हैंडलर का संदर्भ वापस कर सकते हैं।

4

मैंने इस समस्या को हल करने के लिए टेम्पलेट्स का उपयोग किया है (और दूसरों के बारे में सुना है)। विचार यह है कि आप अपने विभिन्न प्रोटोकॉल को मूल परमाणु संचालन में विभाजित करते हैं और फिर अलग-अलग ब्लॉक से प्रोटोकॉल बनाने के लिए boost::fusion::vector जैसे कुछ का उपयोग करते हैं।

पीछा कर रहा है एक बहुत ही किसी न किसी तरह (लापता टुकड़े के बहुत सारे) उदाहरण:

// These are the kind of atomic operations that we can do: 
struct read_string { /* ... */ }; 
struct write_string { /* ... */ }; 
struct read_int { /* ... */ }; 
struct write_int { /* ... */ }; 

// These are the different protocol versions 
typedef vector<read_string, write_int> Protocol1; 
typedef vector<read_int, read_string, write_int> Protocol2; 
typedef vector<read_int, write_int, read_string, write_int> Protocol3; 

// This type *does* the work for a given atomic operation 
struct DoAtomicOp { 
    void operator()(read_string & rs) const { ... } 
    void operator()(write_string & ws) const { ... } 
    void operator()(read_int & ri) const { ... } 
    void operator()(write_int & wi) const { ... } 
}; 

template <typename Protocol> void doProtWork (...) { 
    Protocl prot; 
    for_each (prot, DoAtomicOp (...)); 
} 

क्योंकि protocl संस्करण गतिशील है आप निर्धारित करने के लिए उपयोग करने के लिए protocl एकल शीर्ष स्तर स्विच बयान की आवश्यकता होगी।

void doWork (int protocol ...) { 
    switch (protocol) { 
    case PROTOCOL_1: 
    doProtWork<Protocol1> (...); 
    break; 
    case PROTOCOL_2: 
    doProtWork<Protocol2> (...); 
    break; 
    case PROTOCOL_3: 
    doProtWork<Protocol3> (...); 
    break; 
    }; 
} 

एक नए प्रोटोकॉल को जोड़ने के लिए (कि मौजूदा प्रकार का उपयोग करता है) आप केवल protocl अनुक्रम को परिभाषित करने की जरूरत है:

typedef vector<read_int, write_int, write_int, write_int> Protocol4; 

और फिर स्विच बयान में एक नई प्रविष्टि जोड़ें।

+0

यह एक बहुत ही रोचक दृष्टिकोण है, धन्यवाद! – Thomi

0

मैं पीट किर्कहम से सहमत हूं, मुझे लगता है कि विभिन्न प्रोटोकॉल संस्करणों का समर्थन करने के लिए कक्षाओं के संभावित 6 अलग-अलग संस्करणों को बनाए रखने के लिए यह बहुत अप्रिय होगा। यदि आप ऐसा कर सकते हैं, ऐसा लगता है कि पुराने संस्करणों को नवीनतम प्रोटोकॉल में अनुवाद करने के लिए केवल एडाप्टर को लागू करना बेहतर होगा ताकि आपके पास बनाए रखने के लिए केवल एक कार्यरत प्रोटोकॉल हो। आप अभी भी ऊपर दिखाए गए विरासत पदानुक्रम का उपयोग कर सकते हैं, लेकिन पुराने प्रोटोकॉल कार्यान्वयन केवल एक अनुकूलन करते हैं तो नवीनतम प्रोटोकॉल को कॉल करें।

संबंधित मुद्दे