ठोस सिद्धांतों के बारे में हर डेवलपर को पता होना चाहिए

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



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

SOLID क्या है?


यहाँ बताया गया है कि संक्षिप्त SOLID किस प्रकार है:

  • एस: एकल जिम्मेदारी सिद्धांत।
  • ओ: ओपन-क्लोज्ड सिद्धांत।
  • L: Liskov प्रतिस्थापन सिद्धांत (बारबरा Liskov प्रतिस्थापन सिद्धांत)।
  • I: इंटरफ़ेस अलगाव सिद्धांत।
  • डी: निर्भरता उलटा सिद्धांत।

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

एकमात्र जिम्मेदारी का सिद्धांत


“वन एंड। बस एक बात। ” - लोकी ने फिल्म थोर: रग्नारोक में स्कर्गे को बताया।
प्रत्येक वर्ग को केवल एक समस्या का समाधान करना चाहिए।

एक वर्ग को केवल एक चीज के लिए जिम्मेदार होना चाहिए। यदि कोई वर्ग कई समस्याओं को हल करने के लिए ज़िम्मेदार है, तो उसके सबसिस्टम जो इन समस्याओं के समाधान को लागू करते हैं, एक-दूसरे से संबंधित हैं। इस तरह के एक सबसिस्टम में बदलाव से दूसरे में बदलाव आता है।

ध्यान दें कि यह सिद्धांत न केवल कक्षाओं पर लागू होता है, बल्कि व्यापक अर्थों में सॉफ्टवेयर घटकों पर भी लागू होता है।

उदाहरण के लिए, इस कोड पर विचार करें:

class Animal {    constructor(name: string){ }    getAnimalName() { }    saveAnimal(a: Animal) { } } 

यहाँ प्रस्तुत Animal वर्ग किसी प्रकार के जानवर का वर्णन करता है। यह वर्ग एकमात्र जिम्मेदारी के सिद्धांत का उल्लंघन करता है। इस सिद्धांत का वास्तव में उल्लंघन कैसे हुआ है?

एकमात्र जिम्मेदारी के सिद्धांत के अनुसार, एक वर्ग को केवल एक कार्य को हल करना होगा। वह saveAnimal विधि में डेटा वेयरहाउस के साथ काम करके और कंस्ट्रक्टर में ऑब्जेक्ट के गुणों में हेरफेर करके और getAnimalName विधि में दोनों को हल करता है।

इस तरह के एक वर्ग की संरचना कैसे समस्याओं को जन्म दे सकती है?

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

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

 class Animal {   constructor(name: string){ }   getAnimalName() { } } class AnimalDB {   getAnimal(a: Animal) { }   saveAnimal(a: Animal) { } } 

इस बारे में स्टीव फ़ेंटन का कहना है: “जब कक्षाओं को डिज़ाइन किया जाता है, तो हमें संबंधित घटकों को एकीकृत करने का प्रयास करना चाहिए, जो कि उन्हीं कारणों से परिवर्तन होते हैं। हमें उन घटकों को अलग करने की कोशिश करनी चाहिए, जिनमें बदलाव विभिन्न कारण हैं। ”

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

खुला-बंद सिद्धांत


सॉफ्टवेयर इकाइयां (कक्षाएं, मॉड्यूल, फ़ंक्शन) विस्तार के लिए खुली होनी चाहिए, लेकिन संशोधन के लिए नहीं।

हम Animal वर्ग पर काम करना जारी रखते हैं।

 class Animal {   constructor(name: string){ }   getAnimalName() { } } 

हम जानवरों की सूची के माध्यम से क्रमबद्ध करना चाहते हैं, जिनमें से प्रत्येक को Animal वर्ग की एक वस्तु द्वारा दर्शाया गया है, और यह पता लगाता है कि वे क्या ध्वनि बनाते हैं। कल्पना करें कि हम AnimalSounds फ़ंक्शन का उपयोग करके इस समस्या को हल करते हैं:

 //... const animals: Array<Animal> = [   new Animal('lion'),   new Animal('mouse') ]; function AnimalSound(a: Array<Animal>) {   for(int i = 0; i <= a.length; i++) {       if(a[i].name == 'lion')           return 'roar';       if(a[i].name == 'mouse')           return 'squeak';   } } AnimalSound(animals); 

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

सरणी में एक नया तत्व जोड़ें:

 //... const animals: Array<Animal> = [   new Animal('lion'),   new Animal('mouse'),   new Animal('snake') ] //... 

उसके बाद, हमें AnimalSound फ़ंक्शन का कोड बदलना होगा:

 //... function AnimalSound(a: Array<Animal>) {   for(int i = 0; i <= a.length; i++) {       if(a[i].name == 'lion')           return 'roar';       if(a[i].name == 'mouse')           return 'squeak';       if(a[i].name == 'snake')           return 'hiss';   } } AnimalSound(animals); 

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

ओपन-क्लोज के सिद्धांत के अनुरूप AnimalSound फ़ंक्शन को कैसे लाया जाए? उदाहरण के लिए, इस तरह:

 class Animal {       makeSound();       //... } class Lion extends Animal {   makeSound() {       return 'roar';   } } class Squirrel extends Animal {   makeSound() {       return 'squeak';   } } class Snake extends Animal {   makeSound() {       return 'hiss';   } } //... function AnimalSound(a: Array<Animal>) {   for(int i = 0; i <= a.length; i++) {       a[i].makeSound();   } } AnimalSound(animals); 

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

नतीजतन, एक जानवर का वर्णन करने वाले प्रत्येक वर्ग की अपनी स्वयं की makeSound विधि होगी, और जब makeSound फ़ंक्शन में जानवरों के साथ एक सरणी पर पुनरावृति होती है, तो AnimalSound सरणी के प्रत्येक तत्व के लिए इस पद्धति को कॉल करने के लिए पर्याप्त होगा।

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

एक अन्य उदाहरण पर विचार करें।

मान लीजिए हमारे पास एक स्टोर है। हम ग्राहकों को इस वर्ग का उपयोग करके 20% की छूट देते हैं:

 class Discount {   giveDiscount() {       return this.price * 0.2   } } 

अब ग्राहकों को दो समूहों में विभाजित करने का निर्णय लिया गया। पसंदीदा ( fav ) ग्राहकों को 20% की छूट दी जाती है, और VIP ग्राहकों ( vip ) को यह छूट दोगुनी होती है, अर्थात - 40%। इस तर्क को लागू करने के लिए, वर्ग को संशोधित करने का निर्णय लिया गया:

 class Discount {   giveDiscount() {       if(this.customer == 'fav') {           return this.price * 0.2;       }       if(this.customer == 'vip') {           return this.price * 0.4;       }   } } 

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

खुलेपन-घनिष्ठता के सिद्धांत के अनुसार इस कोड को संसाधित करने के लिए, हम उस प्रोजेक्ट में एक नया वर्ग जोड़ते हैं जो Discount क्लास का विस्तार करता है। इस नई कक्षा में, हम एक नया तंत्र लागू कर रहे हैं:

 class VIPDiscount: Discount {   getDiscount() {       return super.getDiscount() * 2;   } } 

यदि आप "सुपर-वीआईपी" ग्राहकों को 80% की छूट देने का फैसला करते हैं, तो इसे इस तरह दिखना चाहिए:

 class SuperVIPDiscount: VIPDiscount {   getDiscount() {       return super.getDiscount() * 2;   } } 

जैसा कि आप देख सकते हैं, वर्गों के सशक्तीकरण का उपयोग यहाँ किया जाता है, न कि उनके संशोधन का।

बारबरा लिस्कॉव का प्रतिस्थापन सिद्धांत


यह आवश्यक है कि उपवर्ग अपने सुपरक्लास के विकल्प के रूप में कार्य करें।

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

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

 //... function AnimalLegCount(a: Array<Animal>) {   for(int i = 0; i <= a.length; i++) {       if(typeof a[i] == Lion)           return LionLegCount(a[i]);       if(typeof a[i] == Mouse)           return MouseLegCount(a[i]);       if(typeof a[i] == Snake)           return SnakeLegCount(a[i]);   } } AnimalLegCount(animals); 

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

 //... class Pigeon extends Animal {      } const animals[]: Array<Animal> = [   //...,   new Pigeon(); ] function AnimalLegCount(a: Array<Animal>) {   for(int i = 0; i <= a.length; i++) {       if(typeof a[i] == Lion)           return LionLegCount(a[i]);       if(typeof a[i] == Mouse)           return MouseLegCount(a[i]);        if(typeof a[i] == Snake)           return SnakeLegCount(a[i]);       if(typeof a[i] == Pigeon)           return PigeonLegCount(a[i]);   } } AnimalLegCount(animals); 

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

इन विचारों के साथ सशस्त्र, हम AnimalLegCount फ़ंक्शन को फिर से कर सकते हैं:

 function AnimalLegCount(a: Array<Animal>) {   for(let i = 0; i <= a.length; i++) {       a[i].LegCount();   } } AnimalLegCount(animals); 

अब यह फ़ंक्शन उस प्रकार के ऑब्जेक्ट्स में रुचि नहीं रखता है जो इसे पास किया गया है। वह बस अपने LegCount तरीकों को LegCount है। सभी प्रकार के बारे में वह जानती है कि वह जिन वस्तुओं की प्रक्रिया करती है, वे Animal क्लास या उसके उपवर्गों की होनी चाहिए।

LegCount विधि अब Animal वर्ग में दिखाई देनी चाहिए:

 class Animal {   //...   LegCount(); } 

और उनके उपवर्गों को इस विधि को लागू करने की आवश्यकता है:

 //... class Lion extends Animal{   //...   LegCount() {       //...   } } //... 

नतीजतन, उदाहरण के लिए, Lion वर्ग के एक उदाहरण के लिए LegCount विधि का उपयोग करते समय, इस वर्ग में लागू विधि को कहा जाता है और वास्तव में ऐसी विधि को कॉल करने से क्या उम्मीद की जा सकती है।

अब AnimalLegCount फ़ंक्शन AnimalLegCount यह जानने की आवश्यकता नहीं है कि Animal वर्ग के किसी विशेष उपवर्ग की किस वस्तु से यह प्रक्रिया होती है ताकि इस वस्तु द्वारा दर्शाए गए जानवर के अंगों की संख्या के बारे में जानकारी मिल सके। फ़ंक्शन केवल Animal क्लास के LegCount तरीके को कॉल करता है, क्योंकि इस वर्ग के उपवर्गों को इस पद्धति को लागू करना चाहिए ताकि कार्यक्रम के सही संचालन का उल्लंघन किए बिना, उनका उपयोग किया जा सके।

इंटरफ़ेस जुदाई सिद्धांत


एक विशिष्ट ग्राहक के लिए डिज़ाइन किए गए अत्यधिक विशिष्ट इंटरफेस बनाएं। ग्राहकों को उन इंटरफेस पर निर्भर नहीं होना चाहिए जिनका वे उपयोग नहीं करते हैं।

इस सिद्धांत का उद्देश्य बड़े इंटरफेस के कार्यान्वयन से जुड़ी कमियों को दूर करना है।

Shape इंटरफ़ेस पर विचार करें:

 interface Shape {   drawCircle();   drawSquare();   drawRectangle(); } 

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

 class Circle implements Shape {   drawCircle(){       //...   }   drawSquare(){       //...   }   drawRectangle(){       //...   } } class Square implements Shape {   drawCircle(){       //...   }   drawSquare(){       //...   }   drawRectangle(){       //...   } } class Rectangle implements Shape {   drawCircle(){       //...   }   drawSquare(){       //...   }   drawRectangle(){       //...   } } 

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

मान लें कि हम Shape इंटरफ़ेस में एक और विधि जोड़ने का निर्णय लेते हैं, drawTriangle , जिसे त्रिकोण बनाने के लिए डिज़ाइन किया गया है:

 interface Shape {   drawCircle();   drawSquare();   drawRectangle();   drawTriangle(); } 

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

जैसा कि आप देख सकते हैं, इस दृष्टिकोण के साथ एक वर्ग बनाना असंभव है जो एक सर्कल के आउटपुट के लिए एक विधि लागू करता है, लेकिन एक वर्ग, आयत और त्रिकोण को प्राप्त करने के तरीकों को लागू नहीं करता है। इस तरह के तरीकों को लागू किया जा सकता है ताकि जब वे आउटपुट हों, तो एक त्रुटि को इंगित किया जाए कि ऐसा ऑपरेशन नहीं किया जा सकता है।

इंटरफ़ेस पृथक्करण का सिद्धांत हमें हमारे उदाहरण से Shape जैसी इंटरफेस बनाने के खिलाफ चेतावनी देता है। ग्राहकों (हमारे पास Circle , Square और Rectangle वर्ग हैं) को उन तरीकों को लागू नहीं करना चाहिए जिनका उन्हें उपयोग करने की आवश्यकता नहीं है। इसके अलावा, यह सिद्धांत इंगित करता है कि इंटरफ़ेस को केवल एक कार्य को हल करना चाहिए (इसमें यह एकमात्र जिम्मेदारी के सिद्धांत के समान है), इसलिए इस कार्य के दायरे से परे जाने वाली हर चीज को दूसरे इंटरफ़ेस या इंटरफेस में स्थानांतरित किया जाना चाहिए।

हमारे मामले में, Shape इंटरफ़ेस समाधान की समस्याओं को हल करता है जिसके लिए अलग इंटरफेस बनाना आवश्यक है। इस विचार के बाद, हम विभिन्न विशिष्ट कार्यों को हल करने के लिए अलग-अलग इंटरफेस बनाकर कोड को फिर से तैयार करते हैं:

 interface Shape {   draw(); } interface ICircle {   drawCircle(); } interface ISquare {   drawSquare(); } interface IRectangle {   drawRectangle(); } interface ITriangle {   drawTriangle(); } class Circle implements ICircle {   drawCircle() {       //...   } } class Square implements ISquare {   drawSquare() {       //...   } } class Rectangle implements IRectangle {   drawRectangle() {       //...   } } class Triangle implements ITriangle {   drawTriangle() {       //...   } } class CustomShape implements Shape {  draw(){     //...  } } 

अब ICircle इंटरफ़ेस ICircle उपयोग केवल ड्रॉइंग सर्कल, साथ ही अन्य आकृतियों के लिए अन्य विशेष इंटरफेस के लिए किया जाता है। Shape इंटरफ़ेस को एक सार्वभौमिक इंटरफ़ेस के रूप में उपयोग किया जा सकता है।

निर्भरता उलटा सिद्धांत


निर्भरता की वस्तु एक अमूर्त होनी चाहिए, न कि कुछ विशिष्ट।

  1. ऊपरी स्तर के मॉड्यूल को निचले स्तर के मॉड्यूल पर निर्भर नहीं होना चाहिए। दोनों प्रकार के मॉड्यूल को अमूर्तता पर निर्भर होना चाहिए।
  2. विवरण विवरण पर निर्भर नहीं होना चाहिए। विवरण अमूर्तता पर निर्भर होना चाहिए।

सॉफ्टवेयर विकास की प्रक्रिया में, एक ऐसा क्षण होता है जब एप्लिकेशन की कार्यक्षमता उसी मॉड्यूल के भीतर फिट होना बंद हो जाती है। जब ऐसा होता है, तो हमें मॉड्यूल निर्भरता की समस्या को हल करना होगा। परिणामस्वरूप, उदाहरण के लिए, यह पता चल सकता है कि उच्च-स्तरीय घटक निम्न-स्तर के घटकों पर निर्भर करते हैं।

 class XMLHttpService extends XMLHttpRequestService {} class Http {   constructor(private xmlhttpService: XMLHttpService) { }   get(url: string , options: any) {       this.xmlhttpService.request(url,'GET');   }   post() {       this.xmlhttpService.request(url,'POST');   }   //... } 

यहाँ, Http क्लास एक उच्च-स्तरीय घटक है, और XMLHttpService एक निम्न-स्तरीय घटक है। इस तरह की वास्तुकला निर्भरता उलटा सिद्धांत के खंड ए का उल्लंघन करती है: "उच्च स्तर के मॉड्यूल निचले स्तरों के मॉड्यूल पर निर्भर नहीं होना चाहिए। दोनों प्रकार के मॉड्यूल को अमूर्तता पर निर्भर होना चाहिए। ”

Http वर्ग XMLHttpService वर्ग पर निर्भर होने के लिए मजबूर है। यदि हम नेटवर्क के साथ बातचीत करने के लिए Http वर्ग द्वारा उपयोग किए जाने वाले तंत्र को बदलने का निर्णय लेते हैं, तो मान लें कि यह एक Node.js सेवा होगी या, उदाहरण के लिए, परीक्षण उद्देश्यों के लिए उपयोग की जाने वाली स्टब सेवा, हमें संबंधित कोड को बदलकर Http वर्ग के सभी उदाहरणों को संपादित करना होगा। यह खुलेपन-घनिष्ठता के सिद्धांत का उल्लंघन करता है।

Http वर्ग को यह नहीं पता होना चाहिए कि नेटवर्क कनेक्शन स्थापित करने के लिए वास्तव में क्या उपयोग किया जाता है। इसलिए, हम Connection इंटरफ़ेस बनाएंगे:

 interface Connection {   request(url: string, opts:any); } 

Connection इंटरफ़ेस में request विधि का विवरण शामिल है और हम Http वर्ग में Connection प्रकार तर्क पास करते हैं:

 class Http {   constructor(private httpConnection: Connection) { }   get(url: string , options: any) {       this.httpConnection.request(url,'GET');   }   post() {       this.httpConnection.request(url,'POST');   }   //... } 

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

हम XMLHttpService वर्ग को फिर से XMLHttpService ताकि यह इस इंटरफ़ेस को लागू करे:

 class XMLHttpService implements Connection {   const xhr = new XMLHttpRequest();   //...   request(url: string, opts:any) {       xhr.open();       xhr.send();   } } 

नतीजतन, हम कई कक्षाएं बना सकते हैं जो Connection इंटरफ़ेस को लागू करते हैं और नेटवर्क पर डेटा विनिमय के आयोजन के लिए Http वर्ग में उपयोग के लिए उपयुक्त हैं:

 class NodeHttpService implements Connection {   request(url: string, opts:any) {       //...   } } class MockHttpService implements Connection {   request(url: string, opts:any) {       //...   } } 

जैसा कि आप देख सकते हैं, यहां उच्च-स्तरीय और निम्न-स्तरीय मॉड्यूल अमूर्तता पर निर्भर करते हैं। Http वर्ग (उच्च-स्तरीय मॉड्यूल) Connection इंटरफ़ेस (अमूर्त) पर निर्भर करता है। XMLHttpService , NodeHttpService और MockHttpService (निम्न-स्तरीय मॉड्यूल) भी Connection इंटरफ़ेस पर निर्भर करते हैं।

इसके अलावा, यह ध्यान देने योग्य है कि निर्भरता व्युत्क्रम के सिद्धांत का पालन करते हुए, हम प्रतिस्थापन बारबरा लिस्कॉव के सिद्धांत का पालन करते हैं। अर्थात्, यह पता चला है कि प्रकार XMLHttpService , NodeHttpService और MockHttpService बुनियादी प्रकार के Connection प्रतिस्थापन के रूप में काम कर सकता है।

परिणाम


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

प्रिय पाठकों! क्या आप अपनी परियोजनाओं में ठोस सिद्धांतों का उपयोग करते हैं?

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


All Articles