परिचय: Rust में SOLID क्यों महत्वपूर्ण है?
\nSOLID — ऑब्जेक्ट-ओरिएंटेड डिज़ाइन के पाँच मूलभूत सिद्धांत हैं जो लचीला, रखरखाव योग्य और स्केलेबल कोड बनाने में मदद करते हैं। इस तथ्य के बावजूद कि Rust एक क्लासिक OOP भाषा नहीं है (इसमें सामान्य अर्थों में क्लास इनहेरिटेंस नहीं है), ये सिद्धांत ट्रेट्स, स्ट्रक्चर, जेनेरिक्स और मेमोरी ओनरशिप के कारण यहाँ भी लागू होते हैं। इस लेख में, हम Rust में कोड उदाहरणों के साथ प्रत्येक SOLID सिद्धांत का विश्लेषण करेंगे, दिखाएंगे कि उन्हें कैसे लागू किया जाए, और समझाएंगे कि वे आपके कोड को बेहतर क्यों बनाते हैं।
\n\n1. Single Responsibility Principle (SRP): एकल उत्तरदायित्व का सिद्धांत
\nसार: प्रत्येक मॉड्यूल, स्ट्रक्चर या फ़ंक्शन में बदलाव का केवल एक ही कारण होना चाहिए।
\nRust में, यह अक्सर तब टूटता है जब एक स्ट्रक्चर डेटा संग्रहण, उसके सीरियलाइज़ेशन और बिज़नेस लॉजिक के लिए जिम्मेदार होता है। सही दृष्टिकोण जिम्मेदारी को अलग करना है।
\n\nखराब उदाहरण (SRP का उल्लंघन)
\nstruct User {\n name: String,\n email: String,\n}\n\nimpl User {\n fn save_to_db(&self) {\n // डेटाबेस में सेव करने का लॉजिक\n }\n\n fn send_email(&self, message: &str) {\n // ईमेल भेजने का लॉजिक\n }\n}\nयहाँ User संग्रहण और भेजने दोनों के लिए जिम्मेदार है — बदलाव के दो कारण।
अच्छा उदाहरण (SRP का पालन)
\nstruct User {\n name: String,\n email: String,\n}\n\nstruct UserRepository;\nimpl UserRepository {\n fn save(&self, user: &User) { /* ... */ }\n}\n\nstruct EmailService;\nimpl EmailService {\n fn send(&self, user: &User, message: &str) { /* ... */ }\n}\nअब प्रत्येक स्ट्रक्चर की एक ही जिम्मेदारी है: User — डेटा, UserRepository — संग्रहण, EmailService — भेजना।
2. Open-Closed Principle (OCP): खुले/बंद का सिद्धांत
\nसार: सॉफ़्टवेयर संस्थाएँ विस्तार के लिए खुली होनी चाहिए, लेकिन संशोधन के लिए बंद।
\nRust में, यह ट्रेट्स और जेनेरिक्स के माध्यम से प्राप्त किया जाता है। आप मौजूदा कोड को बदले बिना नया व्यवहार जोड़ सकते हैं।
\n\nउदाहरण: डिस्काउंट सिस्टम
\nमान लीजिए कि हमारे पास डिस्काउंट के साथ मूल्य की गणना करने का एक फ़ंक्शन है। if-else के माध्यम से नए डिस्काउंट प्रकार जोड़ने के बजाय, हम ट्रेट का उपयोग करते हैं:
trait Discount {\n fn apply(&self, price: f64) -> f64;\n}\n\nstruct NoDiscount;\nimpl Discount for NoDiscount {\n fn apply(&self, price: f64) -> f64 { price }\n}\n\nstruct SeasonalDiscount(f64);\nimpl Discount for SeasonalDiscount {\n fn apply(&self, price: f64) -> f64 { price * (1.0 - self.0) }\n}\n\nfn calculate_price(price: f64, discount: &dyn Discount) -> f64 {\n discount.apply(price)\n}\nअब, एक नया डिस्काउंट जोड़ने के लिए (उदाहरण के लिए, BlackFridayDiscount), हमें calculate_price बदलने की आवश्यकता नहीं है — बस Discount ट्रेट को लागू करें।
3. Liskov Substitution Principle (LSP): बारबरा लिस्कोव का प्रतिस्थापन सिद्धांत
\nसार: उपप्रकारों की वस्तुएँ (Rust में, ट्रेट को लागू करने वाले प्रकार) प्रोग्राम की शुद्धता को भंग किए बिना आधार प्रकार की वस्तुओं के साथ विनिमेय होनी चाहिए।
\nRust में, इसका अर्थ है कि यदि आपके पास एक ट्रेट और उसके कई कार्यान्वयन हैं, तो dyn Trait या जेनेरिक्स के साथ काम करने वाला कोड उनमें से किसी को भी प्रतिस्थापित करने पर नहीं टूटना चाहिए।
LSP उल्लंघन का उदाहरण
\ntrait Bird {\n fn fly(&self) -> String;\n}\n\nstruct Sparrow;\nimpl Bird for Sparrow {\n fn fly(&self) -> String { "Sparrow flies".to_string() }\n}\n\nstruct Penguin;\nimpl Bird for Penguin {\n fn fly(&self) -> String { panic!("Penguins can't fly!") }\n}\nयदि हम Sparrow को Penguin से बदलते हैं, तो कोड टूट जाएगा। समाधान — उन विधियों को शामिल न करें जिन्हें सभी उपप्रकारों द्वारा लागू नहीं किया जा सकता। ट्रेट्स को अलग करना बेहतर है:
trait CanFly {\n fn fly(&self) -> Stri