C ++ में निहित: शुरुआती, मध्यवर्ती, उन्नत

यह लेख तीन स्तरों पर विरासत का वर्णन करता है: शुरुआती, मध्यवर्ती और उन्नत। एक्सपर्ट नं। और SOLID के बारे में एक शब्द भी नहीं। ईमानदारी से।


शुरुआती


उत्तराधिकार क्या है?


वंशानुक्रम ओओपी के मूलभूत सिद्धांतों में से एक है। इसके अनुसार, एक वर्ग दूसरे वर्ग के चर और विधियों को अपने अनुसार उपयोग कर सकता है।


एक वर्ग जो डेटा को विरासत में लेता है, उसे उपवर्ग, व्युत्पन्न वर्ग या बाल वर्ग कहा जाता है। एक वर्ग जिससे डेटा या विधियां विरासत में मिली हैं, उसे सुपरक्लास, बेस क्लास या पैरेंट क्लास कहा जाता है। "पैरेंट" और "चाइल्ड" शब्द विरासत को समझने के लिए बेहद उपयोगी हैं। जैसा कि एक बच्चा अपने माता-पिता की विशेषताओं को प्राप्त करता है, व्युत्पन्न वर्ग बेस क्लास के तरीकों और चर प्राप्त करता है।


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


इस उदाहरण में, turn_on() विधि और serial_number चर serial_number Computer उपवर्ग में घोषित या परिभाषित नहीं किया गया था। हालांकि, उनका उपयोग किया जा सकता है क्योंकि वे आधार वर्ग से विरासत में मिले हैं।


महत्वपूर्ण नोट : निजी चर और तरीके विरासत में नहीं मिल सकते हैं।


 #include <iostream> using namespace std; class Device { public: int serial_number = 12345678; void turn_on() { cout << "Device is on" << endl; } private: int pincode = 87654321; }; class Computer: public Device {}; int main() { Computer Computer_instance; Computer_instance.turn_on(); cout << "Serial number is: " << Computer_instance.serial_number << endl; // cout << "Pin code is: " << Computer_instance.pincode << endl; // will cause compile time error return 0; } 

वंशानुक्रम के प्रकार


C ++ में विरासत के कई प्रकार हैं:


  • सार्वजनिक - सार्वजनिक ( public ) और संरक्षित ( protected ) डेटा को इसके उपयोग के स्तर को बदलने के बिना विरासत में मिला है;
  • संरक्षित ( protected ) - सभी विरासत डेटा संरक्षित हो जाते हैं;
  • निजी - सभी विरासत में मिला डेटा निजी हो जाता है।

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


 #include <iostream> using namespace std; class Device { public: int serial_number = 12345678; void turn_on() { cout << "Device is on" << endl; } }; class Computer: private Device { public: void say_hello() { turn_on(); cout << "Welcome to Windows 95!" << endl; } }; int main() { Device Device_instance; Computer Computer_instance; cout << "\t Device" << endl; cout << "Serial number is: "<< Device_instance.serial_number << endl; Device_instance.turn_on(); // cout << "Serial number is: " << Computer_instance.serial_number << endl; // Computer_instance.turn_on(); // will cause compile time error cout << "\t Computer" << endl; Computer_instance.say_hello(); return 0; } 

Computer वर्ग अब किसी भी निजी विधि की तरह turn_on() पद्धति का उपयोग करता है: turn_on() को कक्षा के भीतर से कॉल किया जा सकता है, लेकिन इसे सीधे main से कॉल करने की कोशिश करने से संकलन समय पर त्रुटि हो जाएगी। बेस क्लास Device , turn_on() विधि सार्वजनिक बनी हुई है, और इसे main से कॉल किया जा सकता है।


कंस्ट्रक्टर और डिस्ट्रक्टर्स


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


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


 #include <iostream> using namespace std; class Device { public: // constructor Device() { cout << "Device constructor called" << endl; } // destructor ~Device() { cout << "Device destructor called" << endl; } }; class Computer: public Device { public: Computer() { cout << "Computer constructor called" << endl; } ~Computer() { cout << "Computer destructor called" << endl; } }; class Laptop: public Computer { public: Laptop() { cout << "Laptop constructor called" << endl; } ~Laptop() { cout << "Laptop destructor called" << endl; } }; int main() { cout << "\tConstructors" << endl; Laptop Laptop_instance; cout << "\tDestructors" << endl; return 0; } 

कंस्ट्रक्टर्स: Device -> Computer -> Laptop
विध्वंसक: Laptop -> Computer -> Device


एकाधिक वंशानुक्रम


एकाधिक वंशानुक्रम तब होता है जब एक उपवर्ग में दो या अधिक सुपरक्लास होते हैं। इस उदाहरण में, Laptop वर्ग एक ही समय में Monitor और Computer दोनों को विरासत में मिला है।


 #include <iostream> using namespace std; class Computer { public: void turn_on() { cout << "Welcome to Windows 95" << endl; } }; class Monitor { public: void show_image() { cout << "Imagine image here" << endl; } }; class Laptop: public Computer, public Monitor {}; int main() { Laptop Laptop_instance; Laptop_instance.turn_on(); Laptop_instance.show_image(); return 0; } 

मल्टीपल इनहेरिटेंस इश्यूज


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


 #include <iostream> using namespace std; class Computer { private: void turn_on() { cout << "Computer is on." << endl; } }; class Monitor { public: void turn_on() { cout << "Monitor is on." << endl; } }; class Laptop: public Computer, public Monitor {}; int main() { Laptop Laptop_instance; // Laptop_instance.turn_on(); // will cause compile time error return 0; } 

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


मध्यम


Rhombus की समस्या



हीरे की समस्या भाषाओं में एक क्लासिक समस्या है जो कई विरासतों का समर्थन करती है। यह समस्या तब होती है जब कक्षा B और C विरासत में A , और वर्ग D विरासत में B और C


उदाहरण के लिए, क्लास A , B और C print_letter() पद्धति को परिभाषित करते हैं। यदि print_letter() को क्लास D द्वारा बुलाया जाएगा, तो यह स्पष्ट नहीं है कि किस विधि को बुलाया जाना चाहिए - कक्षा A , B या C की एक विधि C विभिन्न भाषाओं में हीरे के आकार की समस्याओं को हल करने के लिए अलग-अलग दृष्टिकोण हैं। C ++ में, समस्या का समाधान प्रोग्रामर के विवेक पर छोड़ दिया जाता है।

हीरे के आकार की समस्या मुख्य रूप से एक डिजाइन समस्या है, और इसे डिजाइन चरण में प्रदान किया जाना चाहिए। विकास के स्तर पर, इसे निम्नानुसार हल किया जा सकता है:


  • एक विशिष्ट सुपरक्लास की विधि को कॉल करें;
  • एक विशेष सुपरक्लास की वस्तु के रूप में उपवर्ग की वस्तु को देखें;
  • अंतिम चाइल्ड क्लास (कोड उप में, Laptop उपवर्ग में) में समस्याग्रस्त पद्धति को ओवरराइड करें।

 #include <iostream> using namespace std; class Device { public: void turn_on() { cout << "Device is on." << endl; } }; class Computer: public Device {}; class Monitor: public Device {}; class Laptop: public Computer, public Monitor { /* public: void turn_on() { cout << "Laptop is on." << endl; } // uncommenting this function will resolve diamond problem */ }; int main() { Laptop Laptop_instance; // Laptop_instance.turn_on(); // will produce compile time error // if Laptop.turn_on function is commented out // calling method of specific superclass Laptop_instance.Monitor::turn_on(); // treating Laptop instance as Monitor instance via static cast static_cast<Monitor&>( Laptop_instance ).turn_on(); return 0; } 

यदि लैपटॉप में turn_on() पद्धति turn_on() ओवरराइड नहीं किया गया है, तो Laptop_instance.turn_on() को कॉल करने से संकलन त्रुटि हो जाएगी। एक Laptop ऑब्जेक्ट दो turn_on() विधि turn_on() एक साथ एक्सेस कर सकता है: Device:Computer:Laptop.turn_on() और Device:Monitor:Laptop.turn_on()


द डायमंड प्रॉब्लम: कंस्ट्रक्टर्स एंड डिस्ट्रक्टर्स


चूंकि C ++ में, किसी चाइल्ड क्लास के ऑब्जेक्ट को इनिशियलाइज़ करते समय, सभी पैरेंट क्लास के कंस्ट्रक्टर्स को बुलाया जाता है, इसलिए एक और समस्या सामने आती है: बेस क्लास Device के कंस्ट्रक्टर को दो बार कॉल किया जाएगा।


 #include <iostream> using namespace std; class Device { public: Device() { cout << "Device constructor called" << endl; } }; class Computer: public Device { public: Computer() { cout << "Computer constructor called" << endl; } }; class Monitor: public Device { public: Monitor() { cout << "Monitor constructor called" << endl; } }; class Laptop: public Computer, public Monitor {}; int main() { Laptop Laptop_instance; return 0; } 

आभासी विरासत


वर्चुअल इनहेरिटेंस कई बेस क्लास ऑब्जेक्ट्स को इनहेरिटेंस पदानुक्रम में प्रदर्शित होने से रोकता है। इस प्रकार, बेस क्लास Device को केवल एक बार कॉल किया जाएगा, और चाइल्ड क्लास में इसे ओवरराइड किए बिना turn_on() विधि तक पहुंचने के लिए संकलन त्रुटि का कारण नहीं होगा।


 #include <iostream> using namespace std; class Device { public: Device() { cout << "Device constructor called" << endl; } void turn_on() { cout << "Device is on." << endl; } }; class Computer: virtual public Device { public: Computer() { cout << "Computer constructor called" << endl; } }; class Monitor: virtual public Device { public: Monitor() { cout << "Monitor constructor called" << endl; } }; class Laptop: public Computer, public Monitor {}; int main() { Laptop Laptop_instance; Laptop_instance.turn_on(); return 0; } 

नोट : Computer और Monitor कक्षाओं में वर्चुअल इनहेरिटेंस rhomboid इनहेरिटेंस की अनुमति नहीं देगा यदि चाइल्ड क्लास Laptop Device क्लास को वस्तुतः ( class Laptop: public Computer, public Monitor, public Device {}; ) विरासत में नहीं देता है।


सार वर्ग


C ++ में, एक वर्ग जिसमें कम से कम एक शुद्ध आभासी विधि मौजूद है, को सार माना जाता है। यदि चाइल्ड क्लास में वर्चुअल मेथड को ओवरराइड नहीं किया जाता है, तो कोड संकलित नहीं होगा। इसके अलावा, C ++ में एक अमूर्त वर्ग का एक ऑब्जेक्ट बनाना असंभव है - संकलन के दौरान एक प्रयास भी त्रुटि का कारण होगा।


 #include <iostream> using namespace std; class Device { public: void turn_on() { cout << "Device is on." << endl; } virtual void say_hello() = 0; }; class Laptop: public Device { public: void say_hello() { cout << "Hello world!" << endl; } }; int main() { Laptop Laptop_instance; Laptop_instance.turn_on(); Laptop_instance.say_hello(); // Device Device_instance; // will cause compile time error return 0; } 

इंटरफ़ेस


C ++, कुछ OOP भाषाओं के विपरीत, एक इंटरफ़ेस को निरूपित करने के लिए एक अलग कीवर्ड प्रदान नहीं करता है। हालांकि, इंटरफ़ेस का कार्यान्वयन एक शुद्ध अमूर्त वर्ग बनाने से संभव है - एक ऐसा वर्ग जिसमें केवल विधि घोषणाएं हैं। ऐसी कक्षाओं को अक्सर सार बेस क्लास (एबीसी) के रूप में भी जाना जाता है।


 #include <iostream> using namespace std; class Device { public: virtual void turn_on() = 0; }; class Laptop: public Device { public: void turn_on() { cout << "Device is on." << endl; } }; int main() { Laptop Laptop_instance; Laptop_instance.turn_on(); // Device Device_instance; // will cause compile time error return 0; } 

उन्नत


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


एक कार्यान्वित या आंशिक रूप से कार्यान्वित वर्ग से विरासत


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


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


इंटरफ़ेस


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


इंटरफ़ेस: उपयोग उदाहरण


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


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


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


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


निष्कर्ष


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


और SOLID अच्छी है।

Source: https://habr.com/ru/post/hi445948/


All Articles