इंटरफ़ेस और विरासत: दोनों दुनिया के सर्वश्रेष्ठ?


10

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

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

केस 1 (निजी सदस्यों के साथ विरासत, अच्छा एनकैप्सुलेशन, कसकर युग्मित)

class Employee
{
int money_earned;
string name;

public:
 void do_work(){money_earned++;};
 string get_name(return name;);
};


class Nurse : public Employee: 
{
   public:
   void do_work(/*do work. Oops, can't update money_earned. Unaware I have to call superclass' do_work()*/);

};

void HireNurse(Nurse *n)
{
   nurse->do_work();
)

केस 2 (सिर्फ एक इंटरफ़ेस)

class IEmployee
{
     virtual void do_work()=0;
     virtual string get_name()=0;
};

//class Nurse implements IEmployee.
//But now, for each employee, must repeat the get_name() implementation,
//and add a name member string, which breaks DRY.

केस 3: (दोनों दुनिया में सर्वश्रेष्ठ?)

केस 1 के समान । हालाँकि, कल्पना कीजिए कि (काल्पनिक रूप से) C ++ ने उन तरीकों को छोड़कर ओवरराइडिंग विधियों की अनुमति नहीं दी है जो शुद्ध आभासी हैं

इसलिए, केस 1 में , do_work () को ओवरराइड करने से एक संकलन-समय त्रुटि होगी। इसे ठीक करने के लिए, हम do_work () को शुद्ध वर्चुअल के रूप में सेट करते हैं, और एक अलग विधि increment_money_earned () जोड़ते हैं। उदहारण के लिए:

class Employee
{
int money_earned;
string name;

public:
 virtual void do_work()=0;
 void increment_money_earned(money_earned++;);
 string get_name(return name;);
};


class Nurse : public Employee: 
{
   public:
   void do_work(/*do work*/ increment_money_earned(); ); .
};

लेकिन इससे भी समस्याएँ हैं। क्या होगा अगर अब से 3 महीने, जो कोडर एक डॉक्टर कर्मचारी बनाता है, लेकिन वह do_work () में increment_money_earned () कॉल करना भूल जाता है?


प्रश्न:

  • है केस 3 से बेहतर केस 1 ? क्या यह इसलिए है क्योंकि यह 'बेहतर एनकैप्सुलेशन' या 'अधिक शिथिल युग्मित', या कुछ अन्य कारण है?

  • है केस 3 बेहतर करने के लिए केस 2 क्योंकि यह सूखी का पालन हो?


2
... क्या आप अमूर्त वर्ग या क्या कर रहे हैं?
ZJR

जवाबों:


10

भूलने की कॉल-टू-द-सुपरक्लास समस्या को हल करने का एक तरीका यह है कि सुपरक्लास को नियंत्रण वापस दे दिया जाए! मैंने अपना पहला उदाहरण फिर से दिखाया है कि कैसे (और इसे संकलित किया है?)। ओह, मैं भी मान लेते हैं कि do_work()में Employeeहोना चाहिए था virtualअपने पहले उदाहरण में।

#include <string>

using namespace std;

class Employee
{
    int money_earned;
    string name;
    virtual void on_do_work() {}

    public:
        void do_work() { money_earned++; on_do_work(); }
        string get_name() { return name; }
};

class Nurse : public Employee
{
    void on_do_work() { /* do more work. Oh, and I don't have to call do_work()! */ }
};

void HireNurse(Nurse* nurse)
{
    nurse->do_work();
}

अब do_work()ओवरराइड नहीं किया जा सकता। यदि आप इसे विस्तारित करना चाहते हैं तो आपको इसे करना होगा on_do_work()जिसके माध्यम से इस do_work()पर नियंत्रण हो।

यह, ज़ाहिर है, इंटरफ़ेस के साथ आपके दूसरे उदाहरण से उपयोग किया जा सकता है और साथ ही Employeeइसे विस्तारित भी करता है। इसलिए, अगर मैं आपको सही ढंग से समझता हूं तो मुझे लगता है कि यह केस 3 बनाता है, लेकिन बिना काल्पनिक सी ++ का उपयोग किए बिना! यह DRY है और इसमें मजबूत एनकैप्सुलेशन है।


3
और वह डिज़ाइन पैटर्न "टेम्पलेट विधि" ( en.wikipedia.org/wiki/Template_method_pattern ) के रूप में जाना जाता है।
जॉरिस टिम्मरमैन

हां, यह केस 3-शिकायत है। यह आशाजनक लग रहा है। विस्तार से जांच करेंगे। इसके अलावा, यह किसी प्रकार की घटना प्रणाली है। क्या इस 'पैटर्न' का कोई नाम है?
मुस्तफा

@MadKeithV क्या आप सुनिश्चित हैं कि यह 'टेम्पलेट विधि' है?
मुस्तफा

@illmath - हाँ, यह एक गैर-सार्वजनिक सार्वजनिक तरीका है जो इसके कार्यान्वयन के विवरण के कुछ हिस्सों को आभासी संरक्षित / निजी तरीकों में दर्शाता है।
जॉरिस टिम्मरमैन

@illmath से पहले मैंने इसे एक टेम्पलेट विधि के रूप में नहीं सोचा था लेकिन मेरा मानना ​​है कि यह एक का एक मूल उदाहरण है। मुझे अभी यह लेख मिला है जिसे आप पढ़ना चाहते हैं जहां लेखक का मानना ​​है कि यह अपने नाम के योग्य है: गैर-आभासी इंटरफ़ेस
मुहावरे

1

एक इंटरफ़ेस के साथ समस्या यह है कि इसमें डिफ़ॉल्ट कार्यान्वयन नहीं हो सकता है, जो सांसारिक गुणों के लिए एक दर्द है और DRY को हरा देता है।

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

दूसरी ओर, SOLID आपको बताता है कि हर वर्ग का एक इंटरफ़ेस होना चाहिए।

क्या केस 3 केस 1 से बेहतर है? क्या यह इसलिए है क्योंकि यह 'बेहतर एनकैप्सुलेशन' या 'अधिक शिथिल युग्मित', या कुछ और कारण है?

नहीं, केस 3 केस से बेहतर नहीं है। 1. आपको अपना दिमाग बनाना होगा। यदि आप एक डिफ़ॉल्ट कार्यान्वयन करना चाहते हैं तो ऐसा करें। यदि आप शुद्ध विधि चाहते हैं तो उसके साथ चलें।

क्या होगा अगर अब से 3 महीने, जो कोडर एक डॉक्टर कर्मचारी बनाता है, लेकिन वह do_work () में increment_money_earned () कॉल करना भूल जाता है?

फिर जो कोडर को वह मिलना चाहिए जो असफल इकाई परीक्षणों की अनदेखी करने के लिए योग्य है। उन्होंने इस वर्ग का परीक्षण किया, क्या उन्होंने नहीं किया? :)

एक सॉफ्टवेयर परियोजना के लिए कौन सा मामला सबसे अच्छा है जिसमें कोड की 40,000 लाइनें हो सकती हैं?

एक आकार सभी फिट नहीं है। यह बताना असंभव है कि कौन सा बेहतर है। कुछ मामले ऐसे हैं, जिनमें से एक बेहतर होगा तो दूसरा।

हो सकता है कि आपको अपने खुद के कुछ आविष्कार करने की कोशिश करने के बजाय कुछ डिज़ाइन पैटर्न सीखना चाहिए ।


मुझे बस एहसास हुआ कि आप गैर-आभासी इंटरफ़ेस डिज़ाइन पैटर्न की तलाश कर रहे हैं , क्योंकि यही आपका मामला 3 वर्ग जैसा दिखता है।


टिप्पणी के लिए धन्यवाद। मैंने अपने इरादे को और अधिक स्पष्ट करने के लिए केस 3 को अपडेट किया है।
मुस्तफा

1
मैं तुम्हें यहाँ -1 करने जा रहा हूँ। यह कहने का कोई कारण नहीं है कि सभी इंटरफेस शुद्ध होने चाहिए, या यह कि सभी वर्गों को एक इंटरफ़ेस से विरासत में मिलना चाहिए।
डेडएमजी

@DeadMG ISP
BЈовиead

@VJovic: SOLID और "सब कुछ एक इंटरफ़ेस से प्राप्त होना चाहिए" के बीच एक बड़ा अंतर है।
डेडएमजी

"एक आकार सभी फिट नहीं होता है" और "कुछ डिज़ाइन पैटर्न सीखें" सही हैं - आपके उत्तर के बाकी हिस्से आपके अपने सुझाव का उल्लंघन करते हैं कि एक आकार सभी फिट बैठता है।
जोरिस टिम्मरमैन

0

इंटरफेस में C ++ में डिफ़ॉल्ट कार्यान्वयन हो सकते हैं। ऐसा कुछ भी नहीं कहा गया है कि किसी फ़ंक्शन का डिफ़ॉल्ट कार्यान्वयन केवल अन्य वर्चुअल सदस्यों (और तर्कों) पर निर्भर नहीं करता है, इसलिए किसी भी प्रकार के युग्मन में वृद्धि नहीं करता है।

केस 2 के लिए, DRY यहां सुपरसाइड कर रहा है। एनकैप्सुलेशन आपके प्रोग्राम को विभिन्न कार्यान्वयनों से, परिवर्तन से बचाने के लिए मौजूद है, लेकिन इस मामले में, आपके पास अलग-अलग कार्यान्वयन नहीं हैं। इसलिए YAGNI एनकैप्सुलेशन।

वास्तव में, रन-टाइम इंटरफेस आमतौर पर उनके संकलन-समय समकक्षों से नीच माना जाता है। संकलन-समय के मामले में, आपके पास केस 1 और केस 2 दोनों एक ही बंडल में हो सकते हैं- इसका उल्लेख नहीं करने के लिए यह कई अन्य फायदे हैं। या यहां तक ​​कि रन-टाइम पर, आप बस एक ही लाभ के लिए प्रभावी रूप से कर सकते हैं । ऐसी चीजों से निपटने के कई तरीके हैं।Employee : public IEmployee

Case 3: (best of both worlds?)

Similar to Case 1. However, imagine that (hypothetically)

मैंने पढ़ना बंद कर दिया। YAGNI। C ++ वह है जो C ++ है, और मानक समिति, उत्कृष्ट कारणों से, इस तरह के बदलाव को लागू करने के लिए कभी नहीं, कभी भी।


आप कहते हैं "आपके पास कोई अलग कार्यान्वयन नहीं है"। पर मै करता हू। मेरे पास कर्मचारी का नर्स कार्यान्वयन है, और मेरे पास बाद में अन्य कार्यान्वयन हो सकते हैं (एक डॉक्टर, एक Janitor, आदि)। मैंने केस 3 को अपडेट किया है ताकि यह स्पष्ट हो सके कि मेरा क्या मतलब है।
मुस्तफा

@illmath: लेकिन आपके पास कोई अन्य कार्यान्वयन नहीं है get_name। आपके सभी प्रस्तावित कार्यान्वयन उसी के कार्यान्वयन को साझा करेंगे get_name। इसके अलावा, जैसा कि मैंने कहा, चुनने का कोई कारण नहीं है, आपके पास दोनों हो सकते हैं। इसके अलावा, केस 3 पूरी तरह से बेकार है। आप गैर-शुद्ध वर्चुअल को ओवरराइड कर सकते हैं, इसलिए एक डिज़ाइन के बारे में भूल जाएं जहां आप नहीं कर सकते।
डेडएमजी

न केवल इंटरफेस सी + + में डिफ़ॉल्ट कार्यान्वयन हो सकते हैं, वे डिफ़ॉल्ट कार्यान्वयन हो सकते हैं और फिर भी सार हो सकते हैं! यानी वर्चुअल शून्य IMethod () = 0 {std :: cout << "नी!" << एसटीडी :: एंडल; }
जोरिस टिम्मरमैन

@MadKeithV: मुझे विश्वास नहीं है कि आप उन्हें इनलाइन परिभाषित कर सकते हैं, लेकिन बिंदु अभी भी वही है।
डेडएमजी

@MadKeith: मानो विजुअल स्टूडियो कभी मानक C ++ का विशेष रूप से सटीक प्रतिनिधित्व करता हो।
डेडएमजी

0

क्या केस 3 केस 1 से बेहतर है? क्या यह इसलिए है क्योंकि यह 'बेहतर एनकैप्सुलेशन' या 'अधिक शिथिल युग्मित', या कुछ और कारण है?

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

एक सॉफ्टवेयर परियोजना के लिए कौन सा मामला सबसे अच्छा है जिसमें कोड की 40,000 लाइनें हो सकती हैं।

मैं कहूंगा कि यह पूरी तरह से आपके कार्यान्वयन डिजाइन और उस उद्देश्य पर निर्भर करता है जिसे आप प्राप्त करना चाहते हैं। समस्या को हल करने के आधार पर सार वर्ग और इंटरफेस को लागू किया जाता है।

प्रश्न पर संपादित करें

क्या होगा अगर अब से 3 महीने, जो कोडर एक डॉक्टर कर्मचारी बनाता है, लेकिन वह do_work () में increment_money_earned () कॉल करना भूल जाता है?

यूनिट परीक्षण यह जांचने के लिए किया जा सकता है कि क्या प्रत्येक वर्ग अपेक्षित व्यवहार की पुष्टि करता है। इसलिए यदि उचित इकाई परीक्षण लागू किए जाते हैं, तो जब नई कोडर नई कक्षा को लागू करता है तो बग को रोका जा सकता है।


0

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.