जानिए कैसे डिपेंडेंसी इंजेक्शन कोड को टेस्ट करने, रिफैक्टर करने और बढ़ाने में आसान बनाता है—व्यावहारिक पैटर्न, उदाहरण और आम पिटफॉल्स जिनसे बचना चाहिए।

Dependency Injection (DI) एक सरल विचार है: किसी कोड के उस हिस्से के बजाय जो ज़रूरी चीज़ें उसे चाहिए, आप उन्हें बाहर से देते हैं।
वो “चीज़ें” उसकी डिपेंडेंसीज़ होती हैं—उदाहरण के लिए, डेटाबेस कनेक्शन, पेमेंट सर्विस, क्लॉक, लॉगर, या ईमेल भेजने वाला। अगर आपका कोड इन्हें खुद बनाता है, तो वह चुपके से यह तय कर देता है कि वे कैसे काम करेंगे।
एक ऑफिस में कॉफ़ी मशीन सोचिए। यह पानी, कॉफ़ी बीन्स और बिजली पर निर्भर है।
DI वह दूसरा तरीका है: “कॉफ़ी मशीन” (आपकी क्लास/फ़ंक्शन) अपना काम करती है, जबकि “सप्लाईज़” (डिपेंडेंसीज़) उसे जो सेटअप करता है वह प्रदान करता है।
DI किसी खास फ्रेमवर्क का उपयोग करना ज़रूरी नहीं करता, और यह DI कंटेनर के बराबर नहीं है। आप मैनुअली DI कर सकते हैं — dependencies को पैरामीटर या कंस्ट्रक्टर के जरिए पास करके।
DI “मॉकिंग” भी नहीं है। मॉकिंग वह तरीका है जिससे आप DI का उपयोग टेस्ट में कर सकते हैं, पर DI खुद बस यह डिज़ाइन निर्णय है कि dependencies कहाँ बनती हैं।
जब dependencies बाहर से दी जाती हैं, तो आपका कोड अलग संदर्भों में चलाना आसान हो जाता है: प्रोडक्शन, यूनिट टेस्ट, डेमो, और भविष्य की फीचर्स।
वही लचीलापन मॉड्यूल्स को भी साफ बनाता है: हिस्सों को बदला जा सकता है बिना पूरे सिस्टम को दोबारा वायर किए। नतीजे के तौर पर, टेस्ट तेज़ और स्पष्ट होते हैं (क्योंकि आप सरल स्टैंड-इन लगा सकते हैं), और कोडबेस बदलने में आसान होता है (क्योंकि हिस्से कम उलझे होते हैं)।
टाइट कपलिंग तब होती है जब आपका कोड यह सीधा तय कर देता है कि उसे कौन से अन्य भाग चाहिए। सबसे आम रूप है: बिज़नेस लॉजिक के अंदर new का उपयोग।
सोचिए एक checkout फ़ंक्शन अंदर new StripeClient() और new SmtpEmailSender() करता है। शुरू में यह सुविधाजनक लगता है—सारी ज़रूरतें वहीं मिल जाती हैं। पर यह checkout फ्लो को उन सटीक इम्प्लीमेंटेशन, कॉन्फ़िग रुल्स और निर्माण के तरीके (API कीज़, टाइमआउट, नेटवर्क व्यवहार) के साथ लॉक कर देता है।
यह कपलिंग “छिपी” होती है क्योंकि यह मेथड सिग्नेचर से स्पष्ट नहीं है। फ़ंक्शन सिर्फ़ ऑर्डर प्रोसेस करने जैसा दिखता है, पर वह गुप्त रूप से पेमेंट गेटवे, ईमेल प्रोवाइडर और शायद डेटाबेस कनेक्शन पर निर्भर कर सकता है।
जब डिपेंडेंसीज़ हार्ड-कोडेड हों, तो छोटे बदलाव भी बड़े असर डालते हैं:
हार्ड-कोडेड डिपेंडेंसीज़ यूनिट टेस्ट को असली काम करवा देती हैं: नेटवर्क कॉल, फ़ाइल I/O, क्लॉक्स, रैंडम IDs, या साझा संसाधन। टेस्ट धीमे होते हैं क्योंकि वे अलग-थलग नहीं हैं, और फ्लैकी होते हैं क्योंकि परिणाम टाइमिंग, बाहरी सेवाओं या एक्सेक्यूशन ऑर्डर पर निर्भर करते हैं।
अगर आप ये पैटर्न देखते हैं, तो टाइट कपलिंग पहले ही आपका समय खा रही होगी:
new का छिड़कावDependency Injection इसे एक्सप्लिसिट और स्वैपेबल बनाकर—बिना बिज़नेस नियमों को हर बार फिर से लिखे—इस समस्या का समाधान करता है।
Inversion of Control (IoC) एक साधारण ज़िम्मेदारी परिवर्तन है: एक क्लास को यह ध्यान रखना चाहिए कि क्या करना है, न कि किस तरह वह चीज़ें प्राप्त करता है जिनकी उसे ज़रूरत है।
जब एक क्लास स्वयं अपनी डिपेंडेंसीज़ बनाती है (उदा., new EmailService() या सीधे DB कनेक्शन खोलना), तो वह चुपके से दो काम कर लेती है: बिज़नेस लॉजिक और सेटअप। इससे क्लास बदलने, पुन: उपयोग करने और टेस्ट करने में कठिन हो जाती है।
IoC के साथ, आपका कोड एब्स्ट्रैक्शन्स पर निर्भर करता है—जैसे इंटरफेस या छोटे “कॉन्ट्रैक्ट” टाइप—किसी विशेष इम्प्लीमेंटेशन पर नहीं।
उदाहरण के लिए, एक CheckoutService को जानने की ज़रूरत नहीं कि पेमेंट Stripe, PayPal, या फेक टेस्ट प्रोसेसर द्वारा प्रोसेस हो रहा है। उसे सिर्फ़ चाहिए “कोई जो कार्ड चार्ज कर सके।” यदि CheckoutService एक IPaymentProcessor स्वीकार करता है, तो वह किसी भी इम्प्लीमेंटेशन के साथ काम कर सकता है जो उस कॉन्ट्रैक्ट का पालन करे।
इससे आपका कोर लॉजिक स्थिर रहता है भले ही नीचे के टूल बदलें।
IoC का प्रायोगिक हिस्सा है कि dependency निर्माण को क्लास के बाहर ले जाएँ और उसे पास करें (अक्सर कंस्ट्रक्टर के माध्यम से)। यहाँ DI काम में आता है: DI IoC हासिल करने का एक सामान्य तरीका है।
बदले में:
आपको मिलता है:
परिणाम लचीलापन है: व्यवहार बदलना कॉन्फ़िगरेशन का निर्णय बन जाता है, फिर से लिखना नहीं।
अगर क्लास अपने डिपेंडेंसीज़ नहीं बनाती, तो किसी और को करना होगा। वह "कोई और" composition root होता है: वह जगह जहाँ आपका एप्लिकेशन असेंबल होता है—आम तौर पर स्टार्टअप कोड।
composition root वह जगह है जहाँ आप तय करते हैं, “प्रोडक्शन में RealPaymentProcessor उपयोग करें; टेस्ट में FakePaymentProcessor।” इस वायरिंग को एक जगह रखने से आश्चर्य कम होते हैं और बाकी कोडबेस व्यवहार पर केंद्रित रहता है।
IoC यूनिट टेस्ट को सरल बनाता है क्योंकि आप छोटे, तेज टेस्ट डबल्स दे सकते हैं असली नेटवर्क या डेटाबेस की जगह।
यह रिफैक्टरिंग को भी सुरक्षित बनाता है: जब ज़िम्मेदारियाँ अलग हों, तो इम्प्लीमेंटेशन बदलने से आमतौर पर उन क्लासेज़ को बदलने की ज़रूरत नहीं पड़ती जो उनका उपयोग करती हैं—बशर्ते एब्स्ट्रैक्शन वही रहे।
Dependency Injection (DI) कोई एक तकनीक नहीं है—यह कक्षाओं को उनके डिपेंडेंसीज़ “खिलाने” के कुछ तरीके हैं (जैसे लॉगर, DB क्लाइंट, पेमेंट गेटवे)। जिस शैली को आप चुनते हैं वह स्पष्टता, टेस्टेबिलिटी और दुरुपयोग की संभावनाओं को प्रभावित करती है।
Constructor injection में, डिपेंडेंसीज़ ऑब्जेक्ट बनाने के लिए आवश्यक होती हैं। बड़ा लाभ यह है कि आप उन्हें भूल नहीं सकते।
यह तब सबसे उपयुक्त है जब डिपेंडेंसी:
Constructor injection सबसे स्पष्ट कोड और सबसे सरल यूनिट टेस्ट देता है—क्योंकि आपका टेस्ट कंस्ट्रक्शन समय पर एक फेक या मॉक पास कर सकता है।
कभी-कभी एक डिपेंडेंसी केवल एक ही ऑपरेशन के लिए चाहिए—उदाहरण: अस्थायी फ़ॉर्मेटर, एक विशेष स्ट्रैटेजी, या request-scoped value।
ऐसे मामलों में, इसे मेथड पैरामीटर के रूप में पास करें। इससे ऑब्जेक्ट छोटा रहता है और एक-बार की ज़रूरत को स्थायी फ़ील्ड बनाने से बचाया जाता है।
Setter injection तब सुविधाजनक हो सकता है जब आप कंस्ट्रक्शन समय पर डिपेंडेंसी प्रदान नहीं कर सकते (कुछ फ्रेमवर्क या लेगेसी कोड पाथ)। ट्रेड-ऑफ यह है कि यह आवश्यकताओं को छिपा सकता है: क्लास उपयोग करने योग्य दिख सकती है भले ही वह पूरी तरह कॉन्फ़िगर न हो।
यह अक्सर रनटाइम आश्चर्यों का कारण बनता है (“यह undefined क्यों है?”) और टेस्ट अधिक नाज़ुक बनते हैं क्योंकि सेटअप भूलने में आसान हो जाता है।
यूनिट टेस्ट सबसे उपयोगी तब होते हैं जब वे तेज़, दोहरनीय, और एक व्यवहार पर केन्द्रित हों। जब कोई यूनिट टेस्ट असली डेटाबेस, नेटवर्क कॉल, फ़ाइल सिस्टम या क्लॉक पर निर्भर करता है, तो वह धीमा और फ्लैकी हो जाता है। साथ ही, विफलताएँ कम जानकारीपूर्ण हो जाती हैं: क्या कोड टूट गया, या वातावरण में कुछ गड़बड़ हुआ?
Dependency Injection (DI) इसे ठीक करता है क्योंकि आपका कोड उन चीज़ों को बाहर से स्वीकार करता है जिनपर वह निर्भर है (DB एक्सेस, HTTP क्लाइंट, टाइम प्रोवाइडर)। टेस्ट में आप उन्हीं डिपेंडेंसीज़ को हल्के विकल्पों से बदल सकते हैं।
असली DB या API कॉल सेटअप समय और लेटेंसी जोड़ते हैं। DI के साथ, आप एक इन-मेमोरी रिपॉजिटरी या फेक क्लाइंट इंजेक्ट कर सकते हैं जो तैयार उत्तर तुरंत लौटाता है। इसका मतलब:
DI के बिना, कोड अक्सर अपने डिपेंडेंसीज़ को खुद new() करता है, जिससे टेस्ट को पूरे स्टैक को exercise करना पड़ता है। DI के साथ, आप इंजेक्ट कर सकते हैं:
कोई हैक्स नहीं, कोई ग्लोबल स्विच नहीं—बस एक अलग इम्प्लीमेंटेशन पास करें।
DI सेटअप को स्पष्ट बनाता है। कॉन्फ़िगरेशन, कनेक्शन स्ट्रिंग्स या टेस्ट-ओनली एनवायरनमेंट वेरिएबल्स को खोजना छोड़ दें—आप एक टेस्ट पढ़ कर तुरंत देख सकते हैं क्या असली है और क्या बदला गया है।
एक典typical DI-फ्रेंडली टेस्ट कुछ इस तरह पढ़ता है:
Arrange: सर्विस बनाएँ एक fake रिपॉजिटरी और stubbed क्लॉक के साथ
Act: मेथड कॉल करें
Assert: रिटर्न वैल्यू और/या mock इंटरैक्शन्स वेरिफाई करें
यह सीधेपन शोर को घटाता है और विफलताओं का निदान आसान बनाता है—ठीक वही जो आप यूनिट टेस्ट से चाहते हैं।
एक test seam उस जगह को कहते हैं जहाँ आप जानबूझकर व्यवहार बदलने की गुंजाइश रखते हैं। प्रोडक्शन में आप असली चीज़ लगाएंगे; टेस्ट में आप तेज़, सुरक्षित विकल्प लगाएंगे। Dependency injection इन सीमों को बिना हैक्स के बनाना सबसे सरल तरीका है।
सीम सबसे उपयोगी उन हिस्सों के आसपास होती हैं जिन्हें टेस्ट में नियंत्रित करना कठिन है:
अगर बिज़नेस लॉजिक सीधे इन चीज़ों को कॉल करता है, तो टेस्ट नाज़ुक हो जाते हैं: वे उन कारणों से fail होते हैं जो आपके लॉजिक से सम्बन्धित नहीं होते (नेटवर्क हिचकी, टाइमज़ोन फ़र्क, मिसिंग फ़ाइल्स)।
एक सीम अक्सर इंटरफेस के रूप में होती है—या डायनेमिक भाषाओं में एक सरल "कॉन्ट्रैक्ट" जैसे "इस ऑब्जेक्ट में now() मेथड होना चाहिए।" मुख्य विचार है कि आप जो चाहिए उस पर निर्भर करें, न कि यह कहाँ से आता है पर।
उदाहरण के लिए, सिस्टम क्लॉक को सीधे ऑर्डर सर्विस के अंदर कॉल करने के बजाय आप Clock पर निर्भर कर सकते हैं:
SystemClock.now()FakeClock.now() एक फिक्स्ड समय लौटाता हैउसी पैटर्न का उपयोग फ़ाइल रीड (FileStore), ईमेल भेजना (Mailer), या कार्ड चार्ज करना (PaymentGateway) के लिए करें। आपका कोर लॉजिक वही रहता है; सिर्फ़ प्लग-इन इम्प्लीमेंटेशन बदलता है।
जब आप व्यवहार जानबूझकर बदल सकते हैं:
अच्छी तरह रखी गई सीम भारी मॉकिंग की ज़रूरत घटा देती हैं और कुछ साफ़ बिंदु देती हैं जहाँ प्रतिस्थापन रखें।
मॉड्युलैरिटी का विचार यह है कि सॉफ्टवेयर स्वतंत्र हिस्सों (मॉड्यूल्स) से बना हो जिनकी स्पष्ट सीमाएँ हों: हर मॉड्यूल की जिम्मेदारी फोकस्ड हो और बाकी सिस्टम के साथ इंटरैक्शन का स्पष्ट तरीका हो।
Dependency injection (DI) इस बात का समर्थन करता है कि ये सीमाएँ स्पष्ट रहें। बजाय इसके कि एक मॉड्युल सब कुछ खोजे या बनाए, वह अपनी डिपेंडेंसीज़ बाहर से प्राप्त करता है। यह छोटा सा परिवर्तन यह घटाता है कि एक मॉड्युल दूसरे के बारे में कितना "जानता" है।
जब कोड अंदर से डिपेंडेंसीज़ बनाता है (उदा., सर्विस के अंदर DB क्लाइंट new करना), तो कॉलर और डिपेंडेंसी एक-दूसरे के साथ टाइटली टाईड हो जाते हैं। DI आपको इंटरफेस (या साधारण कॉन्ट्रैक्ट) पर निर्भर रहने के लिए प्रोत्साहित करता है, न कि किसी खास इम्प्लीमेंटेशन पर।
इसका मतलब है कि एक मॉड्युल को आमतौर पर केवल यह जानने की ज़रूरत होती है:
PaymentGateway.charge())नतीजतन, मॉड्यूल कम बार साथ में बदलते हैं, क्योंकि अंदर की चीज़ें सीमाओं के पार नहीं लीक होतीं।
एक मॉड्यूलर कोडबेस आपको एक कॉम्पोनेंट स्वैप करने देता है बिना हर कॉलर को दोबारा लिखे। DI इसे व्यावहारिक बनाता है:
हर स्थिति में, कॉलर्स वही कॉन्ट्रैक्ट इस्तेमाल करते रहते हैं। वायरिंग एक जगह बदलती है (composition root), न कि पूरे कोडबेस में बिखरी हुई एडिट्स।
स्पष्ट डिपेंडेंसी सीम टीमों को समानांतर काम करने योग्य बनाती है। एक टीम एक सहमत इंटरफेस के पीछे नया इम्प्लीमेंटेशन बना सकती है जबकि दूसरी टीम उस इंटरफेस पर निर्भर फीचर विकसित करती है।
DI आंशिक रिफैक्टरिंग को भी समर्थन देता है: आप एक मॉड्युल निकाल सकते हैं, उसे इंजेक्ट कर सकते हैं, और धीरे-धीरे बदल सकते हैं—बिना बड़े-भर के री-राइट के।
कोड में DI देखने से परिभाषा से ज़्यादा समझ आती है। यहाँ एक छोटा नोटिफिकेशन फीचर का before/after है।
जब एक क्लास अंदर new करती है, तो वह तय कर देती है कौन इम्प्लीमेंटेशन उपयोग होगा और कैसे उसे बनाना है。
class EmailService {
send(to, message) {
// talks to real SMTP provider
}
}
class WelcomeNotifier {
notify(user) {
const email = new EmailService();
email.send(user.email, "Welcome!");
}
}
टेस्टिंग दर्द: एक यूनिट टेस्ट असली ईमेल व्यवहार ट्रिगर कर सकता है (या ग्लोबल स्टबिंग की ज़रूरत पड़ती है)।
test("sends welcome email", () => {
const notifier = new WelcomeNotifier();
notifier.notify({ email: "[email protected]" });
// Hard to assert without patching EmailService globally
});
अब WelcomeNotifier किसी भी ऑब्जेक्ट को स्वीकार करता है जो ज़रूरी व्यवहार देता है।
class WelcomeNotifier {
constructor(emailService) {
this.emailService = emailService;
}
notify(user) {
this.emailService.send(user.email, "Welcome!");
}
}
टेस्ट छोटा, तेज़ और स्पष्ट हो जाता है।
test("sends welcome email", () => {
const fakeEmail = { send: vi.fn() };
const notifier = new WelcomeNotifier(fakeEmail);
notifier.notify({ email: "[email protected]" });
expect(fakeEmail.send).toHaveBeenCalledWith("[email protected]", "Welcome!");
});
बाद में SMS जोड़ना चाहते हैं? आप WelcomeNotifier को नहीं छेड़ते। आप बस अलग इम्प्लीमेंटेशन पास कर देते हैं:
const smsService = { send: (to, msg) => {/* SMS provider */} };
const notifier = new WelcomeNotifier(smsService);
यह व्यावहारिक लाभ है: टेस्ट निर्माण विवरणों से नहीं जूझते, और नया व्यवहार डिपेंडेंसी स्वैप करके जोड़ा जाता है बजाय मौजूदा को फिर से लिखे।
DI बस इतना सरल हो सकता है कि "आप जिस चीज़ की ज़रूरत हैं उसे चीज़ में पास कर दें।" यही मैनुअल DI है। DI कंटेनर वह टूल है जो उस वायरिंग को ऑटोमेट करता है। दोनों अच्छे विकल्प हो सकते हैं—चाल यह है कि उस ऑटोमेशन का स्तर चुनें जो आपकी एप्लिकेशन से मेल खाता हो।
मैनुअल DI में आप ऑब्जेक्ट खुद बनाते हैं और कंस्ट्रक्टर के जरिए dependencies पास करते हैं। यह सरल है:
मैनुअल वायरिंग अच्छे डिजाइन आदतें भी मजबूर करती है। अगर किसी ऑब्जेक्ट को सात डिपेंडेंसी चाहिएं, तो आपको तुरंत दर्द महसूस होगा—जो अक्सर जिम्मेदारियों को विभाजित करने का संकेत है।
जैसे-जैसे कॉम्पोनेंट्स की संख्या बढ़ती है, मैनुअल वायरिंग दोहरावदार हो सकती है। DI कंटेनर मदद कर सकता है:
कंटेनर उन्हीं एप्लिकेशन्स में चमकता है जिनमें स्पष्ट बॉउंड्रीज़ और लाइफसाइकल हों—वेब ऐप्स, लॉन्ग-रनिंग सर्विसेज, या सिस्टम जिनके कई फीचर्स साझा इन्फ्रास्ट्रक्चर पर निर्भर हों।
कंटेनर एक भारी-कपल्ड डिज़ाइन को "साफ" महसूस करा सकता है क्योंकि वायरिंग गायब हो जाती है। पर मूल समस्याएं बनी रहती हैं:
अगर कंटेनर जोड़ने से कोड कम पठनीय हो रहा है, या डेवलपर्स यह नहीं जानते कि क्या किस पर निर्भर है, तो आपने शायद ज़्यादा कर दिया।
शुरुआत मैनुअल DI के साथ करें ताकि चीज़ें स्पष्ट रहें जब आप अपने मॉड्यूल्स को आकार दे रहे हों। जब वायरिंग दोहरावदार हो जाए या लाइफसाइकल मैनेजमेंट जटिल हो, तब कंटेनर जोड़ें।
एक व्यावहारिक नियम: कोर/बिज़नेस कोड के अंदर मैनुअल DI रखें, और (वैकल्पिक रूप से) ऐप बॉर्डर पर एक कंटेनर उपयोग करें (composition root) सब कुछ असेंबल करने के लिए। यह आपका डिज़ाइन स्पष्ट रखता है और जब प्रोजेक्ट बढ़े तो बोइलरप्लेट घटाने में मदद करता है।
Dependency injection (DI) को अनुशासन के साथ इस्तेमाल किया जाए तो यह कोड को आसान बनाता है—पर गलत उपयोग से उल्टा भी कर सकता है। यहाँ सबसे सामान्य तरीके हैं जिनसे DI गलत हो जाती है, और उसे उपयोगी बनाए रखने की आदतें।
अगर किसी क्लास को लंबी डिपेंडेंसी सूची चाहिए, तो अक्सर वह बहुत ज़्यादा कर रही होती है। यह DI की विफलता नहीं है—यह DI एक डिज़ाइन स्मेल को उजागर कर रहा है।
व्यवहारिक नियम: अगर आप क्लास का काम एक वाक्य में नहीं बता पा रहे या कंस्ट्रक्टर लगातार बढ़ रहा है, तो क्लास को विभाजित करें, एक छोटा सहयोगी एक्स्ट्रैक्ट करें, या निकट संबंधित ऑपरेशन्स को एक इंटरफेस के पीछे समूहित करें (ध्यान रखें—"god services" ना बनाएं)।
Service Locator पैटर्न अक्सर container.get(Foo) जैसा दिखता है बिज़नेस कोड के अंदर। यह सुविधाजनक लगता है, पर यह डिपेंडेंसीज़ को अदृश्य कर देता है: आप कंस्ट्रक्टर पढ़कर नहीं बता सकते कि क्लास को क्या चाहिए।
टेस्टिंग कठिन हो जाती है क्योंकि आपको ग्लोबल स्टेट (locator) सेटअप करना पड़ता है बजाय स्पष्ट, लोकल फेक्स सप्लाई करने के। स्पष्ट पैरामीटर पास करना बेहतर है (constructor injection सबसे सीधा)।
DI कंटेनर रनटाइम पर फेल कर सकते हैं जब:
ये समस्याएँ परेशान करती हैं क्योंकि वे केवल तब दिखती हैं जब वायरिंग execute होती है।
कंस्ट्रक्टर को छोटा और focused रखें। अगर डिपेंडेंसी सूची बढ़ रही है, तो इसे रिफैक्टर करने का संकेत समझें।
वायरिंग के लिए integration tests रखें। एक हल्का “composition root” टेस्ट जो आपका एप्लिकेशन कंटेनर (या मैनुअल वायरिंग) बनाता है, missing registrations और cycles को जल्दी पकड़ सकता है—प्रोडक्शन से पहले।
अंततः, ऑब्जेक्ट निर्माण को एक जगह रखें (आमतौर पर ऐप स्टार्टअप/composition root) और DI कंटेनर कॉल्स को बिज़नेस लॉजिक से बाहर रखें। यह DI के मुख्य लाभ—कौन किस पर निर्भर है इसकी स्पष्टता—को बचाता है।
Dependency Injection को अपनाना सबसे आसान तब होता है जब आप इसे छोटे, कम-जोखिम वाले रिफैक्टर के रूप में करें। वहां से शुरू करें जहाँ टेस्ट धीमे/फ्लैकी हों, और जहाँ बदलाव अक्सर बिना वजह दूसरे कोड को प्रभावित करते हों।
उन डिपेंडेंसीज़ की तलाश करें जो कोड को टेस्ट करना या समझना कठिन बनाती हैं:
अगर कोई फ़ंक्शन बिना प्रोसेस के बाहर पहुँच के चल नहीं पाता, तो वह आमतौर पर एक अच्छा उम्मीदवार है।
यह तरीका हर बदलाव को समीक्षा योग्य रखता है और किसी भी स्टेप पर आप बिना सिस्टम तोड़े रुक सकते हैं।
DI गलती से कोड को "सब कुछ सब पर निर्भर" बना सकता है अगर आप ज़्यादा इंजेक्ट कर देते हैं।
एक अच्छा नियम: विवरणों की बजाय क्षमताएँ इंजेक्ट करें। उदाहरण के लिए, Clock इंजेक्ट करें बजाय "SystemTime + TimeZoneResolver + NtpClient"। अगर किसी क्लास को पाँच असंबंधित सर्विसेज़ चाहिएं, तो शायद वह बहुत ज़्यादा कर रही है—विभाजित करने पर विचार करें।
इसके अलावा, dependencies को कई लेयरों में "बस इसलिए" पास करने से बचें। जहाँ प्रयोग हो वहीं इंजेक्ट करें; वायरिंग को एक जगह केंद्रीकृत रखें।
अगर आप कोड जनरेटर या "वाइब-कोडिंग" वर्कफ़्लो का उपयोग कर रहे हैं फीचर जल्दी बनाने के लिए, तो DI और भी ज़्यादा मूल्यवान हो जाता है क्योंकि यह संरचना को बनाए रखता है जैसे-जैसे प्रोजेक्ट बढ़ता है। उदाहरण के लिए, जब टीमें Koder.ai जैसी टूल का उपयोग React फ्रंटेंड, Go सर्विसेज, और PostgreSQL-बैक्ड बैकएंड बनाने के लिए करती हैं चैट-ड्रिवन स्पेक से, तो स्पष्ट composition root और DI-फ्रेंडली इंटरफेस यह सुनिश्चित करते हैं कि जनरेटेड कोड टेस्ट करने, रिफैक्टर करने और इंटीग्रेशन (ईमेल, पेमेंट, स्टोरेज) स्वैप करने में आसान रहे—बिना कोर लॉजिक को फिर से लिखे।
नियम वही रहता है: ऑब्जेक्ट निर्माण और एनवायरनमेंट-विशेष वायरिंग को बाउंडरी पर रखें, और बिज़नेस कोड को व्यवहार पर ही केंद्रित रखें।
आपको ठोस सुधार दिखाने चाहिए:
अगर अगला कदम चाहिए, तो अपने "composition root" का डॉक्यूमेंट रखें और उसे साधारण रखें: एक फ़ाइल जो डिपेंडेंसीज़ को वायर करे, जबकि बाकी कोड व्यवहार पर केंद्रित रहे।
Dependency Injection (DI) का मतलब है कि आपका कोड वे चीज़ें बाहर से प्राप्त करता है जिनकी उसे ज़रूरत होती है (डेटाबेस, लॉगर, क्लॉक, पेमेंट क्लाइंट), बजाय इसके कि वह उन्हें खुद बना ले।
व्यावहारिक रूप से यह अक्सर किसी कंस्ट्रक्टर या फ़ंक्शन पैरामीटर में डिपेंडेंसी पास करने जैसा दिखता है, ताकि वे स्पष्ट और बदली जा सकें।
Inversion of Control (IoC) व्यापक विचार है: एक क्लास को यह ध्यान रखना चाहिए कि उसे क्या करना है, न कि अपने सहयोगियों को कैसे प्राप्त करना है।
DI एक सामान्य तकनीक है जो IoC हासिल करने के लिए उपयोग की जाती है—निर्भरता निर्माण को बाहर ले जाकर और इन्हें पास करके।
अगर कोई डिपेंडेंसी new के साथ बिज़नेस लॉजिक के अंदर बनाई जाती है, तो उसे बदलना मुश्किल हो जाता है।
नतीजा होता है:
DI टेस्टों को तेज़ और निर्धारित बनाए रखने में मदद करता है क्योंकि आप असली बाहरी सिस्टम की जगह टेस्ट डबल्स इंजेक्ट कर सकते हैं।
सामान्य प्रतिस्थापन:
DI कंटेनर वैकल्पिक है। शुरुआत में मैनुअल DI (डिपेंडेंसीज़ स्पष्ट रूप से पास करना) से शुरू करें जब:
जब वायरिंग दोहरावदार हो या लाइफसाइकल मैनेजमेंट चाहिए तो कंटेनर पर विचार करें।
Constructor injection तब उपयोग करें जब डिपेंडेंसी ऑब्जेक्ट के काम करने के लिए ज़रूरी हो और कई तरीकों में इस्तेमाल हो।
Method/parameter injection तब जब वह केवल एक कॉल के लिए चाहिए।
Setter/property injection केवल तब जब वास्तव में लेट वायरिंग चाहिए—और उससे पहले fail-fast चेक या वैलिडेशन जोड़ें।
Composition root वह जगह है जहाँ आप एप्लिकेशन को assemble करते हैं: implementations बनाते हैं और उन्हें उन सर्विसेज़ में पास करते हैं जिनको उनकी ज़रूरत है।
इसे ऐप स्टार्टअप (entry point) के पास रखें ताकि बाकी कोड बेस व्यवहार पर केंद्रित रहे, wiring पर नहीं।
Test seam वह जानबूझकर बनाया गया पॉइंट है जहाँ व्यवहार बदला जा सकता है।
Seam के अच्छे स्थान वे होते हैं जो टेस्ट में नियंत्रित करना कठिन होते हैं:
Clock.now())DI इन जगहों पर आपको टेस्ट में एक प्रतिस्थापन इंजेक्ट करने देता है।
सामान्य गलतियाँ:
container.get() बिज़नेस कोड के अंदर कॉल करने से असली डिपेंडेंसीज़ छिप जाती हैं; स्पष्ट पैरामीटर पसंद करें।सुरक्षित तरीके से DI को लागू करने के चरण:
इसे दूसरे seams के लिए दोहराएँ; कभी भी बड़ी रिफ़ैक्टरिंग की ज़रूरत नहीं होगी।