C++ में Dependency Injection का परिचय
C++ में आधुनिक विकास में, लचीला और परीक्षण योग्य कोड बनाने के प्रमुख सिद्धांतों में से एक है Dependency Injection (DI) — निर्भरता इंजेक्शन। यह डिज़ाइन पैटर्न सिस्टम के घटकों के बीच युग्मन (coupling) को कम करने की अनुमति देता है, जिससे कोड अधिक मॉड्यूलर, पुन: प्रयोज्य और परीक्षण में आसान हो जाता है।
इस लेख में, हम समझेंगे कि C++ में Dependency Injection क्या है, यह किन समस्याओं का समाधान करता है, व्यवहार में इसे लागू करने के विभिन्न तरीकों पर विचार करेंगे और कोड उदाहरण देंगे जो आपको इस दृष्टिकोण को अपनी परियोजनाओं में शामिल करने में मदद करेंगे।
प्रत्यक्ष निर्भरता की समस्या
समाधान के बारे में बात करने से पहले, आइए एक सामान्य समस्या पर नज़र डालें। मान लीजिए कि हमारे पास एक UserService क्लास है, जो लॉग रिकॉर्ड करने के लिए Logger का उपयोग करता है:
class Logger {public: void log(const std::string& message) { std::cout << "[LOG]: " << message << std::endl; }};
class UserService {private: Logger logger; // कठोर निर्भरताpublic: void createUser(const std::string& name) { logger.log("Creating user: " + name); // ... उपयोगकर्ता बनाने का तर्क }};यहाँ, UserService स्वयं Logger का एक इंस्टेंस बनाता है। यह कई समस्याएँ पैदा करता है:
- परीक्षण में कठिनाई: यूनिट टेस्ट के दौरान लॉगर को मॉक-ऑब्जेक्ट से बदलना असंभव है।
- कठोर युग्मन (Tight Coupling):
Loggerके कंस्ट्रक्टर में बदलाव के लिए उन सभी क्लासेस में बदलाव की आवश्यकता होगी जो इसका उपयोग करते हैं। - SRP का उल्लंघन (Single Responsibility Principle): क्लास न केवल अपने तर्क के लिए बल्कि निर्भरताएँ बनाने के लिए भी जिम्मेदार है।
Dependency Injection क्या है?
Dependency Injection एक पैटर्न है जिसमें कोई ऑब्जेक्ट अपनी निर्भरताएँ बाहर से प्राप्त करता है, न कि उन्हें स्वयं बनाता है। मुख्य विचार है: "पूछो मत, प्राप्त करो" (Don't ask, get it).
C++ में निर्भरता इंजेक्ट करने के तीन मुख्य तरीके हैं:
- Constructor Injection — कंस्ट्रक्टर के माध्यम से।
- Setter Injection — setter-मेथड के माध्यम से।
- Interface Injection — इंटरफ़ेस के माध्यम से।
1. Constructor Injection (कंस्ट्रक्टर के माध्यम से इंजेक्शन)
यह सबसे सामान्य और पसंदीदा तरीका है। निर्भरताएँ क्लास के कंस्ट्रक्टर में पास की जाती हैं:
#include <memory>#include <iostream>
class ILogger {public: virtual ~ILogger() = default; virtual void log(const std::string& message) = 0;};
class ConsoleLogger : public ILogger {public: void log(const std::string& message) override { std::cout << "[CONSOLE]: " << message << std::endl; }};
class FileLogger : public ILogger {public: void log(const std::string& message) override { // फ़ाइल में लिखने का तर्क std::cout << "[FILE]: " << message << std::endl; }};
class UserService {private: std::unique_ptr<ILogger> logger_; // इंटरफ़ेस के माध्यम से निर्भरताpublic: // Constructor Injection explicit UserService(std::unique_ptr<ILogger> logger) : logger_(std::move(logger)) {}
void createUser(const std::string& name) { logger_->log("Creating user: " + name); // ... तर्क }};
int main() { auto consoleLogger = std::make_unique<ConsoleLogger>(); UserService service(std::move(consoleLogger)); service.createUser("Alice"); return 0;}लाभ:
- निर्भरता कंस्ट्रक्टर के सिग्नेचर में स्पष्ट रूप से दिखाई देती है।
- ऑब्जेक्ट बनने के बाद हमेशा एक वैध स्थिति में होता है।
- परीक्षण करना आसान है — मॉक ऑब्जेक्ट डाला जा सकता है