परिचय: Rust में Dependency Injection क्यों ज़रूरी है?
\n\nRust में प्रोग्राम लिखते समय, आपको अक्सर ऐसी स्थिति का सामना करना पड़ेगा जहां एक मॉड्यूल (जैसे, सर्विस या कंट्रोलर) को दूसरे मॉड्यूल (डेटाबेस, API, लॉगर) की क्षमताओं का उपयोग करना होता है। सबसे आसान तरीका है कि ज़रूरत के समय ऑब्जेक्ट को सीधे कोड के अंदर बना लें:
\n\nstruct UserService;\n\nimpl UserService {\n fn get_user(&self, id: u32) -> String {\n let db = Database::new(); // ❌ टाइट कपलिंग (tight coupling)\n db.query_user(id)\n }\n}\n\n\nयह तरीका टाइट कपलिंग (tight coupling) पैदा करता है। कोड को टेस्ट करना, दोबारा इस्तेमाल करना और बदलना मुश्किल हो जाता है। Dependency Injection (DI) एक डिज़ाइन पैटर्न है जिसमें डिपेंडेंसी (निर्भरता) को ऑब्जेक्ट के अंदर बनाने के बजाय बाहर से दी जाती है। Rust, अपनी ओनरशिप सिस्टम और ट्रेट्स के साथ, बिना किसी फ्रेमवर्क के DI को लागू करने के शानदार तरीके प्रदान करता है।
\n\nइस लेख में, हम व्यावहारिक उदाहरणों के साथ Rust में डिपेंडेंसी इंजेक्ट करना सीखेंगे: स्ट्रक्चर को पास करने से लेकर ट्रेट्स और स्मार्ट पॉइंटर्स के उपयोग तक।
\n\n1. बेसिक तरीका: कंस्ट्रक्टर के ज़रिए डिपेंडेंसी पास करना
\n\nRust में डिपेंडेंसी इंजेक्ट करने का सबसे सरल तरीका है कि इसे कंस्ट्रक्टर या फंक्शन के आर्गुमेंट के रूप में पास करें। आइए एक लॉगर के उदाहरण से समझते हैं।
\n\nखराब उदाहरण (DI के बिना)
\n\nstruct ConsoleLogger;\n\nimpl ConsoleLogger {\n fn log(&self, msg: &str) {\n println!("[LOG]: {}", msg);\n }\n}\n\nstruct OrderService {\n logger: ConsoleLogger, // ❌ एक खास टाइप से बंधा हुआ\n}\n\nimpl OrderService {\n fn new() -> Self {\n Self {\n logger: ConsoleLogger, // अंदर ही बना रहे हैं\n }\n }\n\n fn place_order(&self, item: &str) {\n self.logger.log(&format!("ऑर्डर: {}", item));\n }\n}\n\n\nअच्छा उदाहरण (DI के साथ)
\n\nstruct ConsoleLogger;\n\nimpl ConsoleLogger {\n fn log(&self, msg: &str) {\n println!("[LOG]: {}", msg);\n }\n}\n\nstruct OrderService {\n logger: ConsoleLogger, // डिपेंडेंसी बाहर से आ रही है\n}\n\nimpl OrderService {\n // कंस्ट्रक्टर डिपेंडेंसी को स्वीकार करता है\n fn new(logger: ConsoleLogger) -> Self {\n Self { logger }\n }\n\n fn place_order(&self, item: &str) {\n self.logger.log(&format!("ऑर्डर: {}", item));\n }\n}\n\nfn main() {\n let logger = ConsoleLogger;\n let service = OrderService::new(logger);\n service.place_order("Rust की किताब");\n}\n\n\nक्या बदला? अब OrderService खुद लॉगर नहीं बनाता। उसे तैयार लॉगर मिलता है। यह DI है, लेकिन एक समस्या है: हम अब भी ConsoleLogger टाइप से बंधे हैं। कोड को लचीला बनाने के लिए, हमें ट्रेट्स का उपयोग करना होगा।
2. डिपेंडेंसी को एब्सट्रैक्ट करने के लिए ट्रेट्स का उपयोग
\n\nRust में ट्रेट्स (traits) दूसरी भाषाओं के इंटरफेस की तरह हैं। ये एक कॉन्ट्रैक्ट (अनुबंध) को परिभाषित करते हैं जिसका पालन डिपेंडेंसी को करना होता है।
\n\nLogger ट्रेट को परिभाषित करना
\n\ntrait Logger {\n fn log(&self, msg: &str);\n}\n\nstruct ConsoleLogger;\nimpl Logger for ConsoleLogger {\n fn log(&self, msg: &str) {\n println!("[कंसोल]: {}", msg);\n }\n}\n\nstruct FileLogger {\n filename: String,\n}\nimpl Logger for FileLogger {\n fn log(&self, msg: &str) {\n // फ़ाइल में लिखने का अनुकरण\n println!("[फ़ाइल {}]: {}", self.filename, msg);\n }\n}\n\n\nजेनेरिक्स (generics) के ज़रिए ट्रेट को इंजेक्ट करना
\n\nstruct OrderService<L: Logger> {\n logger: L, // जेनेरिक टाइप जो Logger ट्रेट को लागू करता है\n}\n\nimpl<L: Logger> OrderService<L> {\n fn new(logger: L) -> Self {\n Self { logger }\n }\n\n fn place_order(&self, item: &str) {\n self.logger.log(&format!("ऑर्डर: {}", item));\n }\n}\n\nfn main() {\n let console_logge