2008-12-18 13 views
19

मुझे पता है कि हीरा विरासत को खराब अभ्यास माना जाता है। हालांकि, मेरे पास 2 मामले हैं जिनमें मुझे लगता है कि हीरा विरासत बहुत अच्छी तरह से फिट हो सकती है। मैं पूछना चाहता हूं, क्या आप मुझे इन मामलों में हीरा विरासत का उपयोग करने की सलाह देंगे, या कोई और डिज़ाइन है जो बेहतर हो सकता है।डायमंड विरासत (सी ++)

केस 1: मैं कक्षाएं बनाना चाहता हूं जो मेरे सिस्टम में विभिन्न प्रकार की "क्रियाएं" का प्रतिनिधित्व करते हैं। कार्यों को कई मानकों द्वारा वर्गीकृत किया जाता है:

  • कार्रवाई "पढ़ा" या "लिखें" हो सकती है।
  • कार्रवाई देरी या देरी के बिना हो सकती है (यह केवल 1 पैरामीटर नहीं है। यह व्यवहार को महत्वपूर्ण रूप से बदलता है)।
  • क्रिया का "प्रवाह प्रकार" फ़्लो या फ्लोबी हो सकता है।

मैं निम्नलिखित डिजाइन करने का इरादा:

// abstract classes 
class Action 
{ 
    // methods relevant for all actions 
}; 
class ActionRead  : public virtual Action 
{ 
    // methods related to reading 
}; 
class ActionWrite  : public virtual Action 
{ 
    // methods related to writing 
}; 
class ActionWithDelay : public virtual Action 
{ 
    // methods related to delay definition and handling 
}; 
class ActionNoDelay : public virtual Action {/*...*/}; 
class ActionFlowA  : public virtual Action {/*...*/}; 
class ActionFlowB  : public virtual Action {/*...*/}; 

// concrete classes 
class ActionFlowAReadWithDelay : public ActionFlowA, public ActionRead, public ActionWithDelay 
{ 
    // implementation of the full flow of a read command with delay that does Flow A. 
}; 
class ActionFlowBReadWithDelay : public ActionFlowB, public ActionRead, public ActionWithDelay {/*...*/}; 
//... 
बेशक

, मैं आज्ञा का पालन करेंगे कि कोई भी 2 क्रिया (कार्रवाई वर्ग से इनहेरिट) एक ही विधि को लागू करेगा।

केस 2: मैं अपने सिस्टम में "कमांड" के लिए समग्र डिजाइन पैटर्न को लागू करता हूं। एक कमांड को पढ़ा, लिखा, हटाया जा सकता है, आदि। मैं भी कमांड का अनुक्रम प्राप्त करना चाहता हूं, जिसे पढ़ा, लिखा, हटाया जा सकता है आदि। आदेशों के अनुक्रम में आदेशों के अन्य अनुक्रम शामिल हो सकते हैं।

class CommandAbstraction 
{ 
    CommandAbstraction(){}; 
    ~CommandAbstraction()=0; 
    void Read()=0; 
    void Write()=0; 
    void Restore()=0; 
    bool IsWritten() {/*implemented*/}; 
    // and other implemented functions 
}; 

class OneCommand : public virtual CommandAbstraction 
{ 
    // implement Read, Write, Restore 
}; 

class CompositeCommand : public virtual CommandAbstraction 
{ 
    // implement Read, Write, Restore 
}; 

इसके अलावा, मैं आदेश, "आधुनिक" आदेशों की एक विशेष प्रकार का है:

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

class ModernCommand : public virtual CommandAbstraction 
{ 
    ~ModernCommand()=0; 
    void SetModernPropertyA(){/*...*/} 
    void ExecModernSomething(){/*...*/} 
    void ModernSomethingElse()=0; 

}; 
class OneModernCommand : public OneCommand, public ModernCommand 
{ 
    void ModernSomethingElse() {/*...*/}; 
    // ... few methods specific for OneModernCommand 
}; 
class CompositeModernCommand : public CompositeCommand, public ModernCommand 
{ 
    void ModernSomethingElse() {/*...*/}; 
    // ... few methods specific for CompositeModernCommand 
}; 

फिर, मुझे यकीन है कि कोई 2 वर्गों CommandAbstraction वर्ग से इनहेरिट एक ही विधि को लागू करेगा कर देगा: तो मैं निम्नलिखित डिजाइन करने के लिए (ऊपर के अलावा) चाहते हैं।

धन्यवाद।

उत्तर

19

विरासत सी ++ में दूसरा सबसे मजबूत (अधिक युग्मन) संबंध है, जो केवल दोस्ती से पहले होता है। यदि आप केवल रचना का उपयोग करके फिर से डिजाइन कर सकते हैं तो आपका कोड अधिक कम हो जाएगा। यदि आप नहीं कर सकते हैं, तो आपको यह विचार करना चाहिए कि क्या आपके सभी वर्गों को वास्तव में आधार से प्राप्त होना चाहिए। क्या यह कार्यान्वयन या सिर्फ एक इंटरफेस के कारण है? क्या आप आधार तत्व के रूप में पदानुक्रम के किसी भी तत्व का उपयोग करना चाहते हैं? या सिर्फ आपके पदानुक्रम में पत्तियां हैं जो वास्तविक कार्य हैं? यदि केवल पत्तियां ही कार्य करती हैं और आप व्यवहार जोड़ रहे हैं तो आप व्यवहार के इस प्रकार की रचना के लिए नीति आधारित डिजाइन पर विचार कर सकते हैं।

विचार है कि विभिन्न (ओर्थोगोनल) व्यवहार छोटे वर्ग सेटों में परिभाषित किया जा सकता है और फिर एक साथ बंडल असली पूरा व्यवहार प्रदान करना है। उदाहरण में मैं केवल एक नीति पर विचार करूंगा जो परिभाषित करता है कि कार्रवाई अब या भविष्य में निष्पादित की जानी है, और निष्पादित करने का आदेश।

मैं इतना है कि टेम्पलेट के विभिन्न instantiations एक कंटेनर में संग्रहित किया जा सकता है (संकेत) के माध्यम से या तर्क के रूप में कार्य करने के लिए पारित किया और polymorphically कहा जाता हो एक अमूर्त वर्ग प्रदान करते हैं।

class ActionDelayPolicy_NoWait; 

class ActionBase // Only needed if you want to use polymorphically different actions 
{ 
public: 
    virtual ~Action() {} 
    virtual void run() = 0; 
}; 

template < typename Command, typename DelayPolicy = ActionDelayPolicy_NoWait > 
class Action : public DelayPolicy, public Command 
{ 
public: 
    virtual run() { 
     DelayPolicy::wait(); // inherit wait from DelayPolicy 
     Command::execute(); // inherit command to execute 
    } 
}; 

// Real executed code can be written once (for each action to execute) 
class CommandSalute 
{ 
public: 
    void execute() { std::cout << "Hi!" << std::endl; } 
}; 

class CommandSmile 
{ 
public: 
    void execute() { std::cout << ":)" << std::endl; } 
}; 

// And waiting behaviors can be defined separatedly: 
class ActionDelayPolicy_NoWait 
{ 
public: 
    void wait() const {} 
}; 

// Note that as Action inherits from the policy, the public methods (if required) 
// will be publicly available at the place of instantiation 
class ActionDelayPolicy_WaitSeconds 
{ 
public: 
    ActionDelayPolicy_WaitSeconds() : seconds_(0) {} 
    void wait() const { sleep(seconds_); } 
    void wait_period(int seconds) { seconds_ = seconds; } 
    int wait_period() const { return seconds_; } 
private: 
    int seconds_; 
}; 

// Polimorphically execute the action 
void execute_action(Action& action) 
{ 
    action.run(); 
} 

// Now the usage: 
int main() 
{ 
    Action<CommandSalute> salute_now; 
    execute_action(salute_now); 

    Action< CommandSmile, ActionDelayPolicy_WaitSeconds > smile_later; 
    smile_later.wait_period(100); // Accessible from the wait policy through inheritance 
    execute_action(smile_later); 
} 

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

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

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

नए ऑर्थोगोनल व्यवहार जोड़ते समय कक्षाओं की संख्या में घातीय वृद्धि का अर्थ होगा यदि सादा विरासत का उपयोग किया जाता है (बहुरूपता के साथ), इस दृष्टिकोण के साथ आप केवल अलग-अलग हिस्से को अलग-अलग कार्यान्वित कर सकते हैं और इसे क्रिया टेम्पलेट में एक साथ चिपका सकते हैं।

उदाहरण के लिए, यदि आप अपने कार्रवाई समय-समय पर बनाने के लिए और एक निकास नीति है कि यह जांच करते हैं समय-समय पर पाश बाहर निकलने के लिए जोड़ सकते हैं। दिमाग में आने वाले पहले विकल्प हैं LoopPolicy_NRuns और LoopPolicy_TimeSpan, LoopPolicy_Until। मेरे पॉलिसी विधि (मेरे मामले में बाहर निकलें)) को प्रत्येक लूप के लिए एक बार बुलाया जाता है। पहला कार्यान्वयन उस समय की गणना करता है जब इसे एक निश्चित संख्या के बाद निकास कहा जाता है (उपयोगकर्ता द्वारा तय किया गया है, क्योंकि उपर्युक्त उदाहरण में अवधि तय की गई थी)। दूसरा कार्यान्वयन समय-समय पर एक निश्चित समय अवधि के लिए प्रक्रिया चलाएगा, जबकि आखिरी व्यक्ति इस प्रक्रिया को किसी दिए गए समय (घड़ी) तक चलाएगा।

आप अभी भी मुझे यहाँ करने के लिए ऊपर का पालन कर रहे हैं, मैं वास्तव में कुछ परिवर्तन करना होगा। पहले एक टेम्पलेट पैरामीटर कमान() को लागू करता है कि एक विधि पर अमल का उपयोग कर रहा functors और शायद एक टेम्प्लेटेड निर्माता है कि आदेश पैरामीटर के रूप में निष्पादित करने के लिए ले जाता है का प्रयोग करेंगे के बजाय है। तर्क यह है कि यह अन्य पुस्तकालयों के साथ संयोजन में अधिक विस्तारशील बना देगा क्योंकि बूस्ट :: बाइंड या बूस्ट :: लैम्ब्डा, क्योंकि उस मामले में आदेश किसी भी मुक्त फ़ंक्शन, फ़ैक्टर या सदस्य विधि को तत्कालता के बिंदु पर बाध्य किया जा सकता है एक वर्ग के

अब मुझे जाना है, लेकिन यदि आप रुचि रखते हैं तो मैं एक संशोधित संस्करण पोस्ट करने का प्रयास कर सकता हूं।

+0

हाय डेविड। मुझे उन्नत ओओपी (विरासत) विषयों को सीखने में दिलचस्पी है जैसा आपने अपने उपरोक्त पदों (मज़दूरों, आभासी कार्यों, आदि) के साथ उल्लेख किया है। क्या आप इस विषय पर किसी भी पुस्तक की सिफारिश कर सकते हैं? –

+0

@ करलो डेल मुंडो: इस [प्रश्न] में पुस्तकों का एक अच्छा संग्रह है (http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) मुझे विशेष रूप से अलेक्जेंड्रेस्कू पढ़ने का आनंद मिला (शुरुआती के लिए नहीं) और सटर। –

9

कार्यान्वयन उन्मुख हीरा विरासत के बीच एक डिज़ाइन-गुणवत्ता अंतर है जहां कार्यान्वयन विरासत में मिला है (जोखिम भरा), और उप-उन्मुख विरासत जहां इंटरफेस या मार्कर-इंटरफेस विरासत में हैं (अक्सर उपयोगी)।

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

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

फिर, इन इंटरफेस के ठोस कार्यान्वयन का एक सेट है जिसे विभिन्न तरीकों से लागू किया जा सकता है (उदा।, एकत्रीकरण, यहां तक ​​कि विरासत)।

इस ढांचे को एकीकृत करें ताकि बाहरी क्लाइंट केवल इंटरफेस प्राप्त कर सकें और ठोस प्रकारों से सीधे बातचीत न करें, और सुनिश्चित करें कि आपके कार्यान्वयन का पूरी तरह से परीक्षण करें।

बेशक, यह बहुत काम है, लेकिन यदि आप एक केंद्रीय और पुन: प्रयोज्य एपीआई लिख रहे हैं, तो यह आपकी सबसे अच्छी शर्त हो सकती है।

5

मैं इस सप्ताह इस समस्या में भाग गया और डीडीजे पर एक लेख पाया जो समस्याओं की व्याख्या करता है और जब आपको उनके बारे में चिंतित होना चाहिए या नहीं। संदेश यह है:

"Multiple Inheritance Considered Useful"

1
पहला उदाहरण के साथ

.....

इसके कि क्या ActionRead ActionWrite पर सभी कार्रवाई की उपवर्गों की जरूरत है।

चूंकि आप एक ठोस वर्ग के साथ समाप्त होने जा रहे हैं, जो किसी भी तरह की कार्रवाई होगी, आप केवल उनके बिना कार्यवाही किए बिना एक्शनread और एक्शनराइट प्राप्त कर सकते हैं।

हालांकि, आप कोड का आविष्कार कर सकते हैं जिसके लिए उन्हें कार्यवाही करने की आवश्यकता होगी। लेकिन आम तौर पर मैं एक्शन, रीड, लिखित, और देरी को अलग और अलग करता हूं और केवल कंक्रीट क्लास सभी को मिलाकर

-1

मामले 2 के लिए, OneCommandCompositeCommand का एक विशेष मामला नहीं है? आप OneCommand को खत्म करने और करने के लिए CompositeCommand रों केवल एक ही तत्व है की अनुमति देते हैं, मैं अपने डिजाइन सरल हो जाता है लगता है:

   CommandAbstraction 
       /  \ 
       /   \ 
      /   \ 
     ModernCommand  CompositeCommand 
       \    /
       \   /
       \   /
      ModernCompositeCommand 

तुम अब भी खतरनाक हीरा है, लेकिन मैं यह इसके लिए एक स्वीकार्य मामला हो सकता है लगता है।

+0

धन्यवाद, लेकिन नहीं, OneCommand और CompositeCommand मूल रूप से अलग हैं। –

1

आप जो कर रहे हैं उससे अधिक जानने के साथ, मैं शायद चीजों को फिर से व्यवस्थित कर दूंगा। कार्रवाई के इन सभी संस्करणों के साथ एकाधिक उत्तराधिकारी के बजाय, मैं प्रतिनिधियों के रूप में instorciated कक्षाओं, polymorphic पढ़ने और लिखने और लिखने के लिए बना देगा।

कुछ की तरह निम्नलिखित (जो कोई हीरा inheritence है):

यहाँ मैं कई मायनों में से एक वैकल्पिक देरी, लागू करने और देरी कार्यप्रणाली मान उपस्थित सभी पाठकों के लिए एक ही है। प्रत्येक उप-वर्ग में देरी का अपना कार्यान्वयन हो सकता है, इस मामले में आप संबंधित व्युत्पन्न विलंब वर्ग के पढ़ने और उदाहरण के लिए पास हो जाएंगे।

class Action // abstract 
{ 
    // Reader and writer would be abstract classes (if not interfaces) 
    // from which you would derive to implement the specific 
    // read and write protocols. 

    class Reader // abstract 
    { 
     Class Delay {...}; 
     Delay *optional_delay; // NULL when no delay 
     Reader (bool with_delay) 
     : optional_delay(with_delay ? new Delay() : NULL) 
     {}; 
     .... 
    }; 

    class Writer {... }; // abstract 

    Reader *reader; // may be NULL if not a reader 
    Writer *writer; // may be NULL if not a writer 

    Action (Reader *_reader, Writer *_writer) 
    : reader(_reader) 
    , writer(_writer) 
    {}; 

    void read() 
    { if (reader) reader->read(); } 
    void write() 
    { if (writer) writer->write(); } 
}; 


Class Flow : public Action 
{ 
    // Here you would likely have enhanced version 
    // of read and write specific that implements Flow behaviour 
    // That would be comment to FlowA and FlowB 
    class Reader : public Action::Reader {...} 
    class Writer : public Action::Writer {...} 
    // for Reader and W 
    Flow (Reader *_reader, Writer *_writer) 
    : Action(_reader,_writer) 
    , writer(_writer) 
    {}; 
}; 

class FlowA :public Flow // concrete 
{ 
    class Reader : public Flow::Reader {...} // concrete 
    // The full implementation for reading A flows 
    // Apparently flow A has no write ability 
    FlowA(bool with_delay) 
    : Flow (new FlowA::Reader(with_delay),NULL) // NULL indicates is not a writer 
    {}; 
}; 

class FlowB : public Flow // concrete 
{ 
    class Reader : public Flow::Reader {...} // concrete 
    // The full implementation for reading B flows 
    // Apparently flow B has no write ability 
    FlowB(bool with_delay) 
    : Flow (new FlowB::Reader(with_delay),NULL) // NULL indicates is not a writer 
    {}; 
}; 
4

इंटरफेस के विरासत पदानुक्रम में "हीरे" काफी सुरक्षित है - यह कोड की विरासत है जो आपको गर्म पानी में ले जाती है।

कोड पुन: उपयोग प्राप्त करने के लिए, मैं आपको मिक्सिन पर विचार करने की सलाह देता हूं (यदि आप tequnique से अपरिचित हैं तो C++ Mixins के लिए Google)। मिश्रणों का उपयोग करते समय आपको लगता है कि आप कोड स्निपेट्स के लिए "शॉपिंग" कर सकते हैं, जिसे आपको राज्य वर्गों के एकाधिक विरासत के बिना कक्षा को लागू करने की आवश्यकता है।

तो, पैटर्न है - कंक्रीट वर्ग को लागू करने में सहायता के लिए इंटरफेस की एकाधिक विरासत और मिश्रणों की एक श्रृंखला (आपको कोड पुन: उपयोग)।

आशा है कि मदद करता है!

+0

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

+0

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

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