खोज…


टिप्पणियों

निर्भरता इंजेक्शन द्वारा हल की गई समस्याएं

यदि हम निर्भरता इंजेक्शन का उपयोग नहीं करते हैं, तो Greeter क्लास इस तरह दिख सकता है:

public class ControlFreakGreeter
{
    public void Greet()
    {
        var greetingProvider = new SqlGreetingProvider(
            ConfigurationManager.ConnectionStrings["myConnectionString"].ConnectionString);
        var greeting = greetingProvider.GetGreeting();
        Console.WriteLine(greeting);
    }
}

यह एक "कंट्रोल फ्रीक" है क्योंकि यह ग्रीटिंग प्रदान करने वाले वर्ग को नियंत्रित करता है, यह नियंत्रण करता है कि एसक्यूएल कनेक्शन स्ट्रिंग कहां से आता है, और यह आउटपुट को नियंत्रित करता है।

निर्भरता इंजेक्शन का उपयोग करते हुए, Greeter वर्ग एक जिम्मेदारी के पक्ष में उन जिम्मेदारियों को Greeter , जो इसे प्रदान की गई बधाई लिखता है।

निर्भरता उलटा सिद्धांत बताता है कि वर्गों को अन्य ठोस वर्गों के बजाय अमूर्त (इंटरफेस की तरह) पर निर्भर होना चाहिए। कक्षाओं के बीच प्रत्यक्ष निर्भरता (युग्मन) रखरखाव को उत्तरोत्तर कठिन बना सकते हैं। अमूर्तता के आधार पर उस युग्मन को कम किया जा सकता है।

निर्भरता इंजेक्शन हमें उस निर्भरता व्युत्क्रम को प्राप्त करने में मदद करता है क्योंकि यह उन कक्षाओं को लिखने की ओर जाता है जो अमूर्तता पर निर्भर करते हैं। Greeter वर्ग "जानता है" IGreetingProvider और IGreetingWriter के कार्यान्वयन विवरणों में से कुछ भी नहीं है। यह केवल जानता है कि इंजेक्शन की निर्भरता उन इंटरफेस को लागू करती है। इसका मतलब यह है कि IGreetingProvider और IGreetingWriter को लागू करने वाले ठोस वर्गों में परिवर्तन, Greeter को प्रभावित नहीं करेगा। न ही उन्हें पूरी तरह से अलग कार्यान्वयन के साथ बदल देगा। केवल इंटरफेस में परिवर्तन होगा। Greeter जाता है।

ControlFreakGreeter यूनिट परीक्षण को ठीक से करना असंभव है। हम कोड की एक छोटी इकाई का परीक्षण करना चाहते हैं, लेकिन इसके बजाय हमारे परीक्षण में एसक्यूएल से कनेक्ट करना और संग्रहीत प्रक्रिया को निष्पादित करना शामिल होगा। इसमें कंसोल आउटपुट का परीक्षण भी शामिल होगा। क्योंकि ControlFreakGreeter अन्य वर्गों से अलगाव में परीक्षण करना असंभव है।

Greeter इकाई परीक्षण के लिए आसान है क्योंकि हम इसकी निर्भरता के नकली कार्यान्वयन को इंजेक्ट कर सकते हैं जो संग्रहीत प्रक्रिया को कॉल करने या कंसोल के आउटपुट को पढ़ने की तुलना में निष्पादित और सत्यापित करना आसान है। यह app.config में एक कनेक्शन स्ट्रिंग की आवश्यकता नहीं है।

IGreetingProvider और IGreetingWriter ठोस कार्यान्वयन अधिक जटिल हो सकते हैं। बदले में, उनकी अपनी निर्भरताएं हो सकती हैं जो उन्हें इंजेक्ट की जाती हैं। (उदाहरण के लिए, हम SQL कनेक्शन स्ट्रिंग को SqlGreetingProvider में SqlGreetingProvider ।) लेकिन यह जटिलता अन्य वर्गों से "छिपी" है जो केवल इंटरफेस पर निर्भर करती है। इससे "रिपल इफ़ेक्ट" के बिना एक वर्ग को संशोधित करना आसान हो जाता है, जिससे हमें अन्य वर्गों के अनुरूप परिवर्तन करने की आवश्यकता होती है।

निर्भरता इंजेक्शन - सरल उदाहरण

इस वर्ग को Greeter कहते हैं। इसकी जिम्मेदारी ग्रीटिंग का उत्पादन करना है। इसकी दो निर्भरताएँ हैं । इसे कुछ ऐसा चाहिए जो इसे आउटपुट के लिए ग्रीटिंग प्रदान करे, और फिर इसे उस ग्रीटिंग को आउटपुट करने का एक तरीका चाहिए। उन निर्भरताओं को इंटरफेस, IGreetingProvider और IGreetingWriter दोनों के रूप में वर्णित किया गया है। इस उदाहरण में, उन दो निर्भरताओं को Greeter में "इंजेक्ट" किया जाता है। (उदाहरण के बाद आगे की व्याख्या।)

public class Greeter
{
    private readonly IGreetingProvider _greetingProvider;
    private readonly IGreetingWriter _greetingWriter;

    public Greeter(IGreetingProvider greetingProvider, IGreetingWriter greetingWriter)
    {
        _greetingProvider = greetingProvider;
        _greetingWriter = greetingWriter;
    }

    public void Greet()
    {
        var greeting = _greetingProvider.GetGreeting();
        _greetingWriter.WriteGreeting(greeting);
    }
}

public interface IGreetingProvider
{
    string GetGreeting();
}

public interface IGreetingWriter
{
    void WriteGreeting(string greeting);
}

Greeting वर्ग IGreetingProvider और IGreetingWriter दोनों पर निर्भर करता है, लेकिन यह दोनों के उदाहरण बनाने के लिए ज़िम्मेदार नहीं है। इसके बजाय उन्हें इसके निर्माता में इसकी आवश्यकता है। जो कुछ भी Greeting का एक उदाहरण बनाता है, उसे उन दो निर्भरताओं को प्रदान करना चाहिए। हम निर्भरता को "इंजेक्शन" कह सकते हैं।

क्योंकि इसके निर्माता में वर्ग को निर्भरता प्रदान की जाती है, इसलिए इसे "कंस्ट्रक्टर इंजेक्शन" भी कहा जाता है।

कुछ सामान्य सम्मेलन:

  • निर्माता private क्षेत्रों के रूप में निर्भरता को बचाता है। जैसे ही कक्षा को तत्काल किया जाता है, वे निर्भरताएं कक्षा के अन्य सभी गैर-स्थिर तरीकों के लिए उपलब्ध होती हैं।
  • private क्षेत्र आसानी से readonly जाते हैं। एक बार जब वे निर्माता में सेट हो जाते हैं तो उन्हें बदला नहीं जा सकता। यह इंगित करता है कि उन फ़ील्ड को कंस्ट्रक्टर के बाहर संशोधित (और नहीं) किया जाना चाहिए। यह आगे सुनिश्चित करता है कि वे निर्भरताएँ जीवन भर के लिए उपलब्ध होंगी।
  • निर्भरता इंटरफेस है। यह कड़ाई से आवश्यक नहीं है, लेकिन आम है क्योंकि यह निर्भरता के एक कार्यान्वयन को दूसरे के साथ स्थानापन्न करना आसान बनाता है। यह इकाई परीक्षण उद्देश्यों के लिए इंटरफ़ेस का एक नकली संस्करण प्रदान करने की अनुमति देता है।

कैसे निर्भरता इंजेक्शन इकाई परीक्षण आसान बनाता है

इस के पिछले उदाहरण पर बनाता है Greeter वर्ग है जो दो निर्भरता, है IGreetingProvider और IGreetingWriter

IGreetingProvider का वास्तविक कार्यान्वयन एपीआई कॉल या डेटाबेस से एक स्ट्रिंग प्राप्त कर सकता है। IGreetingWriter का कार्यान्वयन कंसोल में ग्रीटिंग प्रदर्शित कर सकता है। लेकिन क्योंकि Greeter की अपनी निर्भरता इसके निर्माता में इंजेक्ट की गई है, इसलिए एक यूनिट टेस्ट लिखना आसान है जो उन इंटरफेस के नकली संस्करणों को इंजेक्ट करता है। वास्तविक जीवन में हम Moq जैसे ढांचे का उपयोग कर सकते हैं, लेकिन इस मामले में मैं उन नकली कार्यान्वयनों को लिखूंगा।

public class TestGreetingProvider : IGreetingProvider
{
    public const string TestGreeting = "Hello!";

    public string GetGreeting()
    {
        return TestGreeting;
    }
}

public class TestGreetingWriter : List<string>, IGreetingWriter
{
    public void WriteGreeting(string greeting)
    {
        Add(greeting);
    }
}

[TestClass]
public class GreeterTests
{
    [TestMethod]
    public void Greeter_WritesGreeting()
    {
        var greetingProvider = new TestGreetingProvider();
        var greetingWriter = new TestGreetingWriter();
        var greeter = new Greeter(greetingProvider, greetingWriter);
        greeter.Greet();
        Assert.AreEqual(greetingWriter[0], TestGreetingProvider.TestGreeting);
    }
}

IGreetingProvider और IGreetingWriter का व्यवहार इस परीक्षण के लिए प्रासंगिक नहीं है। हम यह Greeter चाहते हैं कि Greeter को एक अभिवादन मिलता है और वह इसे लिखता है। Greeter का डिजाइन (निर्भरता इंजेक्शन का उपयोग करके) हमें किसी भी जटिल चलती भागों के बिना नकली निर्भरता को इंजेक्ट करने की अनुमति देता है। हम सभी परीक्षण कर रहे हैं कि Greeter उन निर्भरताओं के साथ बातचीत करता है जैसा कि हम इसकी अपेक्षा करते हैं।

हम डिपेंडेंसी इंजेक्शन कंटेनर (IoC कंटेनर) का उपयोग क्यों करते हैं

निर्भरता इंजेक्शन का मतलब है कि कक्षाएं लिखना ताकि वे अपनी निर्भरता को नियंत्रित न करें - इसके बजाय, उनकी निर्भरता उन्हें ("इंजेक्शन") प्रदान की जाती है।

यह कैसल विंडसर, ऑटोफाक, सिम्पल इन्जेक्टोर, निनॉज, यूनिटी, या अन्य जैसे एक निर्भरता इंजेक्शन फ्रेमवर्क (जिसे अक्सर "डीआई कंटेनर", "आईओसी कंटेनर", या सिर्फ "कंटेनर" कहा जाता है) का उपयोग करने के समान नहीं है।

एक कंटेनर सिर्फ निर्भरता इंजेक्शन को आसान बनाता है। उदाहरण के लिए, मान लें कि आप कई कक्षाएं लिखते हैं जो निर्भरता इंजेक्शन पर निर्भर करते हैं। एक वर्ग कई इंटरफेस पर निर्भर करता है, उन इंटरफेस को लागू करने वाले वर्ग अन्य इंटरफेस पर निर्भर करते हैं, और इसी तरह। कुछ विशिष्ट मूल्यों पर निर्भर करते हैं। और सिर्फ मनोरंजन के लिए, उन वर्गों में से कुछ IDisposable लागू करते हैं और उनका निपटान करने की आवश्यकता होती है।

प्रत्येक व्यक्ति वर्ग अच्छी तरह से लिखा और परीक्षण करने में आसान है। लेकिन अब एक अलग समस्या है: एक वर्ग का उदाहरण बनाना और अधिक जटिल हो गया है। मान लीजिए हम एक CustomerService वर्ग का एक उदाहरण बना रहे हैं। इसमें निर्भरताएँ हैं और इसकी निर्भरताएँ निर्भरताएँ हैं। एक उदाहरण का निर्माण कुछ इस तरह हो सकता है:

public CustomerData GetCustomerData(string customerNumber)
{
    var customerApiEndpoint = ConfigurationManager.AppSettings["customerApi:customerApiEndpoint"];
    var logFilePath = ConfigurationManager.AppSettings["logwriter:logFilePath"];
    var authConnectionString = ConfigurationManager.ConnectionStrings["authorization"].ConnectionString;
    using(var logWriter = new LogWriter(logFilePath ))
    {
        using(var customerApiClient = new CustomerApiClient(customerApiEndpoint))
        {
            var customerService = new CustomerService(
                new SqlAuthorizationRepository(authorizationConnectionString, logWriter),
                new CustomerDataRepository(customerApiClient, logWriter),
                logWriter
            );   
            
            // All this just to create an instance of CustomerService!         
            return customerService.GetCustomerData(string customerNumber);
        }
    }
}

आप आश्चर्यचकित हो सकते हैं, कि पूरे विशाल निर्माण को एक अलग फ़ंक्शन में क्यों नहीं रखा गया है जो सिर्फ CustomerService देता है? एक कारण यह है कि क्योंकि प्रत्येक वर्ग के लिए निर्भरताएँ इसमें इंजेक्ट की जाती हैं, एक वर्ग यह जानने के लिए ज़िम्मेदार नहीं होता है कि क्या वे निर्भरताएँ IDisposable या उनका निपटान नहीं कर रहे हैं। यह सिर्फ उनका उपयोग करता है। इसलिए यदि हमारे पास एक GetCustomerService() फ़ंक्शन है जो पूरी तरह से निर्मित CustomerService सेवा GetCustomerService() , तो उस वर्ग में कई डिस्पोजेबल संसाधन हो सकते हैं और उन्हें एक्सेस या डिस्पोज करने का कोई तरीका नहीं हो सकता है।

और एक तरफ से आईडीसोप्लॉज़ IDisposable से अलग, उस जैसे नेस्टेड कंस्ट्रक्टर की एक श्रृंखला को कौन कॉल करना चाहता है? यह एक छोटा उदाहरण है। यह बहुत, बहुत बुरा हो सकता है। दोबारा, इसका मतलब यह नहीं है कि हमने कक्षाओं को गलत तरीके से लिखा है। कक्षाएं व्यक्तिगत रूप से परिपूर्ण हो सकती हैं। चुनौती उन्हें एक साथ मिलकर बना रही है।

एक निर्भरता इंजेक्शन कंटेनर सरल करता है। यह हमें यह निर्दिष्ट करने की अनुमति देता है कि प्रत्येक निर्भरता को पूरा करने के लिए किस वर्ग या मूल्य का उपयोग किया जाना चाहिए। यह थोड़ा ओवरसाइप्लाइज़्ड उदाहरण कैसल विंडसर का उपयोग करता है:

var container = new WindsorContainer()
container.Register(
    Component.For<CustomerService>(),
    Component.For<ILogWriter, LogWriter>()
        .DependsOn(Dependency.OnAppSettingsValue("logFilePath", "logWriter:logFilePath")),
    Component.For<IAuthorizationRepository, SqlAuthorizationRepository>()
        .DependsOn(Dependency.OnValue(connectionString, ConfigurationManager.ConnectionStrings["authorization"].ConnectionString)),
    Component.For<ICustomerDataProvider, CustomerApiClient>()
         .DependsOn(Dependency.OnAppSettingsValue("apiEndpoint", "customerApi:customerApiEndpoint"))   
);

हम इसे "पंजीकरण निर्भरता" या "कंटेनर को कॉन्फ़िगर करना" कहते हैं। अनुवादित, यह हमारे WindsorContainer बताता है:

  • यदि किसी वर्ग को ILogWriter आवश्यकता है, तो LogWriter का एक उदाहरण बनाएं। LogWriter को एक फ़ाइल पथ की आवश्यकता होती है। AppSettings से इस मूल्य का उपयोग करें।
  • यदि किसी वर्ग को IAuthorizationRepository आवश्यकता IAuthorizationRepository , तो IAuthorizationRepository एक उदाहरण SqlAuthorizationRepository । इसके लिए कनेक्शन स्ट्रिंग की आवश्यकता होती है। इस मूल्य का उपयोग ConnectionStrings अनुभाग से करें।
  • यदि किसी वर्ग को ICustomerDataProvider आवश्यकता ICustomerDataProvider , तो एक CustomerApiClient बनाएं और इसे स्ट्रिंग प्रदान करें जो इसे AppSettings से AppSettings

जब हम कंटेनर से एक निर्भरता का अनुरोध करते हैं तो हम उस "समाधान" पर निर्भरता कहते हैं। यह बुरा है कि सीधे कंटेनर का उपयोग करना, लेकिन यह एक अलग कहानी है। प्रदर्शन उद्देश्यों के लिए, अब हम ऐसा कर सकते हैं:

var customerService = container.Resolve<CustomerService>();
var data = customerService.GetCustomerData(customerNumber);
container.Release(customerService);

कंटेनर को पता है कि CustomerService IAuthorizationRepository और ICustomerDataProvider पर निर्भर करता है। यह जानता है कि उन आवश्यकताओं को पूरा करने के लिए इसे किन वर्गों की आवश्यकता है। बदले में, उन कक्षाओं में अधिक निर्भरता होती है, और कंटेनर जानता है कि उन्हें कैसे पूरा किया जाए। यह CustomerService का एक उदाहरण वापस कर सकता है जब तक यह जरूरत है हर वर्ग पैदा करेगा।

यदि यह उस बिंदु पर IDoesSomethingElse जाता है जहां एक वर्ग को एक निर्भरता की आवश्यकता होती है जिसे हमने पंजीकृत नहीं किया है, जैसे IDoesSomethingElse , तो जब हम CustomerService को हल करने का प्रयास करते हैं तो यह एक स्पष्ट अपवाद को फेंक देगा, जो यह बताता है कि हमने उस आवश्यकता को पूरा करने के लिए कुछ भी पंजीकृत नहीं किया है।

प्रत्येक DI फ्रेमवर्क कुछ अलग तरह से व्यवहार करता है, लेकिन आम तौर पर वे हमें कुछ नियंत्रण देते हैं कि कुछ कक्षाएं कैसे त्वरित होती हैं। उदाहरण के लिए, क्या हम चाहते हैं कि यह LogWriter का एक उदाहरण बनाए और इसे हर उस वर्ग को प्रदान करे जो ILogWriter पर निर्भर करता है, या क्या हम चाहते हैं कि यह हर बार एक नया निर्माण करे? अधिकांश कंटेनरों में यह निर्दिष्ट करने का एक तरीका है।

उन वर्गों के बारे में क्या जो IDisposable लागू करते हैं? इसलिए हम container.Release(customerService); कहते हैं। कृपया container.Release(customerService); अतं मै। अधिकांश कंटेनर (विंडसर सहित) बनाई गई निर्भरता के सभी के माध्यम से वापस आ जाएंगे और Dispose करने वाले को निपटाना होगा। अगर CustomerService IDisposable तो वह भी निपटान करेगा।

जैसा कि ऊपर देखा गया निर्भरता दर्ज करना सिर्फ लिखने के लिए अधिक कोड की तरह लग सकता है। लेकिन जब हमारे पास बहुत सारी निर्भरता के साथ बहुत सारी कक्षाएं हैं तो यह वास्तव में भुगतान करता है। और अगर हमें निर्भरता इंजेक्शन का उपयोग किए बिना उन्हीं कक्षाओं को लिखना पड़ता है, तो बहुत सारी कक्षाओं के साथ एक ही आवेदन को बनाए रखना और परीक्षण करना मुश्किल हो जाएगा।

यह सतह की खरोंच करता है कि हम निर्भरता इंजेक्शन कंटेनरों का उपयोग क्यों करते हैं। हम किसी एक का उपयोग करने के लिए अपने आवेदन को कैसे कॉन्फ़िगर करते हैं (और इसे सही तरीके से उपयोग करते हैं) केवल एक विषय नहीं है - यह कई विषयों है, क्योंकि निर्देश और उदाहरण एक कंटेनर से दूसरे में भिन्न होते हैं।



Modified text is an extract of the original Stack Overflow Documentation
के तहत लाइसेंस प्राप्त है CC BY-SA 3.0
से संबद्ध नहीं है Stack Overflow