पिछले भाग:
हम अपने कोड को स्वच्छ, लचीला और रखरखाव योग्य बनाने के तरीकों पर विचार करना जारी रख रहे हैं। और अब, आइए स्वच्छ वास्तुशिल्प समाधानों की जांच शुरू करें।
SOLID सिद्धांत रॉबर्ट सी. मार्टिन द्वारा पेश किए गए थे और इन्हें व्यापक रूप से ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग और सॉफ़्टवेयर डिज़ाइन में सर्वोत्तम प्रथाओं के रूप में माना जाता है।
इस लेख में, हम व्यावहारिक उदाहरणों पर तीन पहले ठोस सिद्धांतों पर एक नज़र डालेंगे: एकल जिम्मेदारी सिद्धांत, खुला/बंद सिद्धांत, और लिस्कोव प्रतिस्थापन सिद्धांत।
एक वर्ग के पास परिवर्तन का केवल एक ही कारण होना चाहिए
दूसरे शब्दों में, एक वर्ग के पास एक ही जिम्मेदारी या कार्य होना चाहिए। यह महत्वपूर्ण है क्योंकि एकल जिम्मेदारी वाले वर्ग को समझना, संशोधित करना और बनाए रखना आसान होता है। कोड के एक क्षेत्र में परिवर्तन से पूरे सिस्टम पर असर नहीं पड़ेगा, जिससे बग आने का जोखिम कम हो जाएगा।
आइए एक व्यावहारिक उदाहरण और एकल जिम्मेदारी सिद्धांत का पालन करने के तरीकों पर एक नज़र डालें:
// Bad class UserSettingsService { constructor(user: IUser) { this.user = user; } changeSettings(settings: IUserSettings): void { if (this.isUserValidated()) { // ... } } getUserInfo(): Promise<IUserSettings> { // ... } async isUserValidated(): Promise<boolean> { const userInfo = await this.getUserInfo(); // ... } }
इस उदाहरण में, हमारा वर्ग विभिन्न दिशाओं में कार्य करता है: यह संदर्भ स्थापित करता है, इसे बदलता है, और इसे मान्य करता है।
एसआरपी का पालन करने के लिए, हमें इन विभिन्न जिम्मेदारियों को विभाजित करने की आवश्यकता है।
// Better class UserAuth { constructor(user: IUser) { this.user = user; } getUserInfo(): Promise<IUserSettings> { // ... } async isUserValidated(): boolean { const userInfo = await this.getUserInfo(); // ... } } class UserSettings { constructor(user: IUser) { this.user = user; this.auth = new UserAuth(user); } changeSettings(settings: IUserSettings): void { if (this.auth.isUserValidated()) { // ... } } }
एसआरपी का लक्ष्य अंतर-निर्भरता से उत्पन्न होने वाली चुनौतियों को कम करते हुए, कोड मॉड्यूलैरिटी को बढ़ाना है। कोड को फ़ंक्शंस, अलग-अलग कक्षाओं में व्यवस्थित करने और मॉड्यूलरिटी को बढ़ावा देने से, यह अधिक पुन: प्रयोज्य हो जाता है, जिससे समय की बचत होती है जो अन्यथा मौजूदा कार्यक्षमता को फिर से कोड करने में खर्च हो सकता है।
सॉफ़्टवेयर इकाइयाँ (कक्षाएँ, मॉड्यूल, फ़ंक्शंस) विस्तार के लिए खुली होनी चाहिए लेकिन संशोधन के लिए बंद होनी चाहिए
आपको मौजूदा कोड को बदले बिना नई कार्यक्षमता जोड़ने में सक्षम होना चाहिए। यह महत्वपूर्ण है क्योंकि इस सिद्धांत का पालन करके, आप मौजूदा कोड की स्थिरता को जोखिम में डाले बिना अपने सिस्टम में नई सुविधाएँ या घटक पेश कर सकते हैं।
यह कोड पुन: प्रयोज्य को बढ़ावा देता है और कार्यक्षमता का विस्तार करते समय व्यापक परिवर्तनों की आवश्यकता को कम करता है।
// Bad class Product { id: number; name: string[]; price: number; protected constructor(id: number, name: string[], price: number) { this.id = id; this.name = name; this.price = price; } } class Ananas extends Product { constructor(id: number, name: string[], price: number) { super(id, name, price); } } class Banana extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } } class HttpRequestCost { constructor(product: Product) { this.product = product; } getDeliveryCost(): number { if (product instanceOf Ananas) { return requestAnanas(url).then(...); } if (product instanceOf Banana) { return requestBanana(url).then(...); } } } function requestAnanas(url: string): Promise<ICost> { // logic for ananas } function requestBanana(url: string): Promise<ICost> { // logic for bananas }
इस उदाहरण में, समस्या वर्ग HttpRequestCost
में है, जिसमें getDeliveryCost
विधि में विभिन्न प्रकार के उत्पादों की गणना के लिए शर्तें शामिल हैं, और हम प्रत्येक प्रकार के उत्पाद के लिए अलग-अलग तरीकों का उपयोग करते हैं। इसलिए, यदि हमें एक नए प्रकार का उत्पाद जोड़ने की आवश्यकता है, तो हमें HttpRequestCost
वर्ग को संशोधित करना चाहिए, और यह सुरक्षित नहीं है; हमें अप्रत्याशित परिणाम मिल सकते हैं.
इससे बचने के लिए, हमें बिना किसी अहसास के Product
वर्ग में एक अमूर्त विधि request
बनाना चाहिए। विशेष बोध को ये वर्ग विरासत में मिले होंगे: अनानास और केला। उन्हें अपने लिए अनुरोध का एहसास होगा।
HttpRequestCost
Product
वर्ग इंटरफ़ेस के बाद product
पैरामीटर लेगा, और जब हम HttpRequestCost
में विशेष निर्भरता पास करते हैं, तो यह पहले से ही अपने लिए request
विधि का एहसास कर लेगा।
// Better abstract class Product { id: number; name: string[]; price: string; constructor(id: number, name: string[], price: string) { this.id = id; this.name = name; this.price = price; } abstract request(url: string): void; } class Ananas extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } request(url: string): void { // logic for ananas } } class Banana extends Product { constructor(id: number, name: string[], price: string) { super(id, name, price); } request(url: string): void { // logic for bananas } } class HttpRequestCost { constructor(product: Product) { this.product = product; } request(): Promise<void> { return this.product.request(url).then(...); } }
इस सिद्धांत का पालन करके, आप कोड युग्मन को कम करेंगे और सिस्टम को अप्रत्याशित व्यवहार से बचाएंगे।
सुपरक्लास की वस्तुओं को एप्लिकेशन को तोड़े बिना उसके उपवर्गों की वस्तुओं से बदला जाना चाहिए।
इस सिद्धांत को समझने के लिए, आइए इस उदाहरण पर एक नज़र डालें:
// Bad class Worker { work(): void {/../} access(): void { console.log('Have an access to closed perimeter'); } } class Programmer extends Worker { createDatabase(): void {/../} } class Seller extends Worker { sale(): void {/../} } class Designer extends Worker { access(): void { throwError('No access'); } }
इस उदाहरण में, हमारे पास Contractor
वर्ग के साथ एक समस्या है। Designer
, Programmer
और Seller
सभी श्रमिक हैं, और उन्हें मूल वर्ग Worker
से विरासत में मिला है। लेकिन साथ ही, डिजाइनरों के पास बंद परिधि तक पहुंच नहीं है क्योंकि वे ठेकेदार हैं, कर्मचारी नहीं। और हमने access
विधि को ओवरराइड कर दिया है और लिस्कोव प्रतिस्थापन सिद्धांत को तोड़ दिया है।
यह सिद्धांत हमें बताता है कि यदि हम सुपरक्लास Worker
उसके उपवर्ग, उदाहरण के लिए Designer
वर्ग, से प्रतिस्थापित करते हैं, तो कार्यक्षमता नहीं टूटनी चाहिए। लेकिन अगर हम ऐसा करते हैं, तो Programmer
वर्ग की कार्यक्षमता टूट जाएगी - access
विधि में Designer
वर्ग से अप्रत्याशित प्राप्ति होगी।
लिस्कोव प्रतिस्थापन सिद्धांत का पालन करते हुए, हमें उपवर्गों में अहसासों को दोबारा नहीं लिखना चाहिए बल्कि अमूर्तता की नई परतें बनाने की जरूरत है जहां हम प्रत्येक प्रकार के अमूर्तन के लिए विशेष अहसासों को परिभाषित करते हैं।
आइए इसे ठीक करें:
// Better class Worker { work(): void {/../} } class Employee extends Worker { access(): void { console.log('Have an access to closed perimeter'); } } class Contractor extends Worker { addNewContract(): void {/../} } class Programmer extends Employee { createDatabase(): void {/../} } class Saler extends Employee { sale(): void {/../} } class Designer extends Contractor { makeDesign(): void {/../} }
हमने अमूर्त Employee
और Contractor
की नई परतें बनाईं और access
पद्धति को Employee
वर्ग में स्थानांतरित कर दिया और विशिष्ट अहसास को परिभाषित किया। यदि हम Worker
क्लास को सबक्लास Contractor
से बदल देते हैं, तो Worker
कार्यक्षमता नहीं टूटेगी।
यदि आप एलएसपी का पालन करते हैं, तो आप किसी भी उपवर्ग उदाहरण को प्रतिस्थापित कर सकते हैं जहां बेस क्लास उदाहरण अपेक्षित है, और प्रोग्राम को अभी भी इच्छित के अनुसार काम करना चाहिए। यह कोड के पुन: उपयोग, मॉड्यूलरिटी को बढ़ावा देता है और आपके कोड को परिवर्तनों के प्रति अधिक लचीला बनाता है।
अगले लेख में, हम इंटरफ़ेस पृथक्करण और निर्भरता व्युत्क्रम SOLID सिद्धांतों पर एक नज़र डालेंगे।