निर्भरता इंजेक्शन का उपयोग कैसे करें और अस्थायी युग्मन से बचें?


11

मान लीजिए कि मेरे पास Serviceकंस्ट्रक्टर के माध्यम से निर्भरता प्राप्त है, लेकिन इसे इस्तेमाल करने से पहले कस्टम डेटा (संदर्भ) के साथ आरंभ करने की आवश्यकता है:

public interface IService
{
    void Initialize(Context context);
    void DoSomething();
    void DoOtherThing();
}

public class Service : IService
{
    private readonly object dependency1;
    private readonly object dependency2;
    private readonly object dependency3;

    public Service(
        object dependency1,
        object dependency2,
        object dependency3)
    {
        this.dependency1 = dependency1 ?? throw new ArgumentNullException(nameof(dependency1));
        this.dependency2 = dependency2 ?? throw new ArgumentNullException(nameof(dependency2));
        this.dependency3 = dependency3 ?? throw new ArgumentNullException(nameof(dependency3));
    }

    public void Initialize(Context context)
    {
        // Initialize state based on context
        // Heavy, long running operation
    }

    public void DoSomething()
    {
        // ...
    }

    public void DoOtherThing()
    {
        // ...
    }
}

public class Context
{
    public int Value1;
    public string Value2;
    public string Value3;
}

अब - संदर्भ डेटा को पहले से पता नहीं है, इसलिए मैं इसे एक निर्भरता के रूप में पंजीकृत नहीं कर सकता हूं और इसे सेवा में इंजेक्ट करने के लिए DI का उपयोग कर सकता हूं

ग्राहक इस तरह का उदाहरण देता है:

public class Client
{
    private readonly IService service;

    public Client(IService service)
    {
        this.service = service ?? throw new ArgumentNullException(nameof(service));
    }

    public void OnStartup()
    {
        service.Initialize(new Context
        {
            Value1 = 123,
            Value2 = "my data",
            Value3 = "abcd"
        });
    }

    public void Execute()
    {
        service.DoSomething();
        service.DoOtherThing();
    }
}

जैसा कि आप देख सकते हैं - इसमें टेम्पोरल कपलिंग और इनिशियलाइज़ मेथड कोड की महक शामिल है, क्योंकि मुझे सबसे पहले कॉल service.Initializeकरने में सक्षम होना चाहिए service.DoSomethingऔर service.DoOtherThingबाद में कॉल करने में सक्षम होना चाहिए ।

अन्य दृष्टिकोण क्या हैं जिनमें मैं इन समस्याओं को समाप्त कर सकता हूं?

व्यवहार का अतिरिक्त स्पष्टीकरण:

ग्राहक के प्रत्येक उदाहरण को ग्राहक के विशिष्ट संदर्भ डेटा के साथ आरंभ की गई सेवा का अपना उदाहरण होना चाहिए। इसलिए, यह संदर्भ डेटा पहले से स्थिर या ज्ञात नहीं है, इसलिए इसे कंस्ट्रक्टर में DI द्वारा इंजेक्ट नहीं किया जा सकता है।

जवाबों:


18

आरंभीकरण समस्या से निपटने के कई तरीके हैं:

  • जैसा कि https://softwareengineering.stackexchange.com/a/334994/301401 में बताया गया है , init () विधियाँ एक कोड गंध हैं। किसी ऑब्जेक्ट को प्रारंभ करना कंस्ट्रक्टर की जिम्मेदारी है - यही कारण है कि आखिरकार हमारे पास कंस्ट्रक्टर हैं।
  • Add दी गई सेवा कोClient कंस्ट्रक्टर के डॉक कमेंट में इनिशियलाइज़ किया जाना चाहिए और यदि सर्विस इनिशियलाइज़ नहीं हुई है तो कंस्ट्रक्टर को फेंक दें। यह जिम्मेदारी उसी को देता है जो आपको IServiceवस्तु देता है ।

हालाँकि, आपके उदाहरण में, Clientकेवल वही है जो उन मानों को जानता है जिन्हें पारित किया जाता है Initialize()। यदि आप इसे इस तरह रखना चाहते हैं, तो मैं निम्नलिखित सुझाव दूंगा:

  • एक जोड़ें IServiceFactoryऔर इसे Clientकंस्ट्रक्टर को पास करें । फिर आप कॉल कर सकते हैं serviceFactory.createService(new Context(...))जो आपको एक इनिशियलाइज्ड देता है जो IServiceआपके क्लाइंट द्वारा उपयोग किया जा सकता है।

कारखाने बहुत सरल हो सकते हैं और आपको init () के तरीकों से बचने और इसके बजाय निर्माणकर्ताओं का उपयोग करने की अनुमति दे सकते हैं:

public interface IServiceFactory
{
    IService createService(Context context);
}

public class ServiceFactory : IServiceFactory
{
    public Service createService(Context context)
    {
        return new Service(context);
    }
}

क्लाइंट में, OnStartup()एक इनिशियलाइज़ेशन मेथड है (यह सिर्फ एक अलग नाम का उपयोग करता है)। इसलिए यदि संभव हो (यदि आप Contextडेटा जानते हैं ), तो कारखाने को सीधे Clientनिर्माता में बुलाया जाना चाहिए । यदि यह संभव नहीं है, तो आपको IServiceFactoryइसे स्टोर करने और इसे कॉल करने की आवश्यकता है OnStartup()

जब उन्हें डीआई द्वारा प्रदान की गई Serviceनिर्भरता नहीं प्रदान की Clientजाती है ServiceFactory:

public interface IServiceFactory
{
    IService createService(Context context);
}    

public class ServiceFactory : IServiceFactory
{        
    private readonly object dependency1;
    private readonly object dependency2;
    private readonly object dependency3;

    public ServiceFactory(object dependency1, object dependency2, object dependency3)
    {
        this.dependency1 = dependency1;
        this.dependency2 = dependency2;
        this.dependency3 = dependency3;
    }

    public Service createService(Context context)
    {
        return new Service(context, dependency1, dependency2, dependency3);
    }
}

1
आपको धन्यवाद, जैसे मैंने सोचा था, आखिरी बिंदु में ... और ServiceFactory में, क्या आप निर्माणकर्ता DI का उपयोग स्वयं कारखाने में सेवा निर्माणकर्ता या सेवा लोकेटर के लिए आवश्यक निर्भरता के लिए अधिक उपयुक्त होगा?
दुसान

1
@ डसन सेवा लोकेटर का उपयोग नहीं करता है। यदि Serviceइसके अलावा अन्य निर्भरताएं हैं , तो वे Contextप्रदान नहीं करेंगे Client, उन्हें डीआई के माध्यम से प्रदान किया जा सकता ServiceFactoryहै Serviceजब createServiceकहा जाता है।
मि। मिंडोर

@Dusan यदि आपको अलग-अलग सेवाओं के लिए अलग-अलग निर्भरता की आपूर्ति करने की आवश्यकता है (यानी: यह एक निर्भरता 1_1 की जरूरत है लेकिन अगले एक को dependency1_2 की आवश्यकता है), लेकिन अगर यह पैटर्न अन्यथा आपके लिए काम करता है, तो आप इसी तरह के पैटर्न का उपयोग कर सकते हैं जिसे अक्सर एक बिल्ड पैटर्न कहा जाता है। एक बिल्डर आपको आवश्यक होने पर समय के साथ एक ऑब्जेक्ट टुकड़ा सेट करने की अनुमति देता है। फिर आप ऐसा कर सकते हैं ... ServiceBuilder partial = new ServiceBuilder().dependency1(dependency1_1).dependency2(dependency2_1).dependency3(dependency3_1);और अपनी आंशिक रूप से स्थापित सेवा के साथ छोड़ दिया जाए, फिर बाद में करेंService s = partial.context(context).build()
हारून

1

Initializeविधि से हटाया जाना चाहिए IService, इंटरफेस के रूप में यह एक कार्यान्वयन विस्तार है। इसके बजाय, एक अन्य वर्ग को परिभाषित करें जो सेवा का ठोस उदाहरण लेता है और उस पर प्रारंभिक विधि कहता है। तब यह नया वर्ग IService इंटरफ़ेस लागू करता है:

public class ContextDependentService : IService
{
    public ContextDependentService(Context context, Service service)
    {
        this.service = service;

        service.Initialize(context);
    }

    // Methods in the IService interface
}

यह क्लाइंट कोड को इनिशियलाइज़ेशन प्रक्रिया से अनभिज्ञ रखता है, सिवाय इसके कि जहाँ ContextDependentServiceक्लास को इनिशियलाइज़ किया जाता है। आप कम से कम अपने आवेदन के उन हिस्सों को सीमित करते हैं, जिन्हें इस विजली आरंभीकरण प्रक्रिया के बारे में जानना आवश्यक है।


1

यह मुझे लगता है कि आपके पास दो विकल्प हैं

  1. प्रारंभ कोड को संदर्भ में ले जाएं और एक प्रारंभिक संदर्भ को इंजेक्ट करें

जैसे।

public InitialisedContext Initialise()
  1. अगर यह पहले से ही नहीं किया गया है तो एक्सिक्यूट कॉल इनिशियल को पहला कॉल करें

जैसे।

public async Task Execute()
{
     //lock context
     //check context is not initialised
     // init if required
     //execute code...
}
  1. अगर आप एक्सक्यूट कहते हैं, तो केवल अपवादों को फेंकना शुरू नहीं किया जाता है। SqlConnection की तरह।

एक फैक्ट्री को इंजेक्ट करना ठीक है यदि आप केवल एक पैरामीटर के रूप में संदर्भ से बचना चाहते हैं। केवल यह कहें कि इस विशेष कार्यान्वयन के संदर्भ की आवश्यकता है और आप इसे इंटरफ़ेस में नहीं जोड़ना चाहते हैं

लेकिन आप अनिवार्य रूप से एक ही समस्या है, क्या होगा अगर कारखाना अभी तक एक प्रारंभिक संदर्भ नहीं मिला है।


0

आपको अपने इंटरफ़ेस को किसी db संदर्भ और प्रारंभिक विधि पर निर्भर नहीं करना चाहिए। आप इसे कंक्रीट क्लास कंस्ट्रक्टर में कर सकते हैं।

public interface IService
{
    void DoSomething();
    void DoOtherThing();
}

public class Service : IService
{
    private readonly object dependency1;
    private readonly object dependency2;
    private readonly object dependency3;
    private readonly object context;

    public Service(
        object dependency1,
        object dependency2,
        object dependency3,
        object context )
    {
        this.dependency1 = dependency1 ?? throw new ArgumentNullException(nameof(dependency1));
        this.dependency2 = dependency2 ?? throw new ArgumentNullException(nameof(dependency2));
        this.dependency3 = dependency3 ?? throw new ArgumentNullException(nameof(dependency3));

        // context is concrete class details not interfaces.
        this.context = context;

        // call init here constructor.
        this.Initialize(context);
    }

    protected void Initialize(Context context)
    {
        // Initialize state based on context
        // Heavy, long running operation
    }

    public void DoSomething()
    {
        // ...
    }

    public void DoOtherThing()
    {
        // ...
    }
}

और, आपके मुख्य प्रश्न का उत्तर प्रॉपर्टी इंजेक्शन होगा

public class Service
    {
        public Service(Context context)
        {
            this.context = context;
        }

        private Dependency1 _dependency1;
        public Dependency1 Dependency1
        {
            get
            {
                if (_dependency1 == null)
                    _dependency1 = Container.Resolve<Dependency1>();

                return _dependency1;
            }
        }

        //...
    }

इस तरह आप प्रॉपर्टी इंजेक्शन द्वारा सभी निर्भरता को बुला सकते हैं । लेकिन यह बहुत बड़ी संख्या हो सकती है। यदि हां, तो आप उनके लिए कंस्ट्रक्टर इंजेक्शन का उपयोग कर सकते हैं, लेकिन यदि यह शून्य है, तो आप चेक करके संपत्ति के बारे में अपना संदर्भ निर्धारित कर सकते हैं।


ठीक है, बढ़िया, लेकिन ... ग्राहक के प्रत्येक उदाहरण के लिए अलग-अलग संदर्भ डेटा के साथ आरंभ की गई सेवा का अपना उदाहरण होना आवश्यक है। यह संदर्भ डेटा पहले से स्थिर या ज्ञात नहीं है, इसलिए इसे कंस्ट्रक्टर में DI द्वारा इंजेक्ट नहीं किया जा सकता है। फिर, मैं अपने ग्राहकों में अन्य निर्भरता के साथ सेवा का उदाहरण कैसे प्राप्त / बना सकता हूं?
दुसान

हम्म उस स्थिर रचनाकार को चलाने से पहले आप संदर्भ सेट करें? और कंस्ट्रक्टर के जोखिम अपवादों में इनिशियलाइज़ करें
इवान

मैं उस इंजेक्शन लगाने वाले कारखाने की ओर झुक रहा हूँ जो दिए गए संदर्भ डेटा के साथ सेवा को बना और आरंभ कर सकता है (बल्कि स्वयं सेवा को इंजेक्ट करने के बजाय), लेकिन मुझे यकीन नहीं है कि बेहतर समाधान हैं।
दुसान

@ इवान तुम सही हो। मैं इसके लिए एक समाधान खोजने की कोशिश करूंगा। लेकिन इससे पहले, मैं इसे अभी के लिए हटा दूंगा।
इंजीनियर

0

मिसको हेवरी के पास आपके द्वारा सामना किए गए मामले के बारे में एक बहुत ही उपयोगी ब्लॉग पोस्ट है। आप दोनों की जरूरत newable और इंजेक्शन आपके लिए Serviceवर्ग और इस ब्लॉग पोस्ट आप मदद कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.