क्या डीआई कंटेनर के माध्यम से बनाई गई वस्तुओं को प्रारंभिक करने के लिए एक पैटर्न है


147

मैं अपनी वस्तुओं के निर्माण का प्रबंधन करने के लिए एकता प्राप्त करने की कोशिश कर रहा हूं और मैं चाहता हूं कि कुछ शुरुआती पैरामीटर हों जो समय-समय पर ज्ञात न हों:

फिलहाल एक ही तरीका है कि मैं यह करने के तरीके के बारे में सोच सकता हूं कि यह इंटरफ़ेस पर एक Init विधि है।

interface IMyIntf {
  void Initialize(string runTimeParam);
  string RunTimeParam { get; }
}

फिर इसका उपयोग करने के लिए (एकता में) मैं यह करूंगा:

var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");

इस परिदृश्य में runTimeParamउपयोगकर्ता इनपुट के आधार पर रन-टाइम पर निर्धारित किया जाता है। यहाँ तुच्छ मामला केवल मान देता है runTimeParamलेकिन वास्तव में पैरामीटर फ़ाइल नाम की तरह कुछ होगा और आरंभिक विधि फ़ाइल के साथ कुछ करेगी।

यह कई मुद्दों को बनाता है, अर्थात् यह Initializeविधि इंटरफ़ेस पर उपलब्ध है और इसे कई बार कहा जा सकता है। कार्यान्वयन में एक झंडा स्थापित करना और Initializeरास्ता भटकाने के लिए दोहराया कॉल पर अपवाद फेंकना ।

उस बिंदु पर जहां मैं अपना इंटरफ़ेस हल करता हूं मैं कार्यान्वयन के बारे में कुछ भी जानना नहीं चाहता IMyIntf। हालांकि, मैं क्या चाहता हूं, यह ज्ञान है कि इस इंटरफ़ेस को कुछ समय के प्रारंभिक मापदंडों की आवश्यकता है। क्या इस जानकारी के साथ किसी भी तरह से एनोटेट (विशेषताएँ?) इंटरफ़ेस है और जब ऑब्जेक्ट बनाया जाता है तो उन्हें पास करना होगा?

संपादित करें: इंटरफ़ेस को कुछ और बताया।


9
आपको DI कंटेनर का उपयोग करने की बात याद आ रही है। निर्भरताएँ आपके लिए हल होने वाली हैं।
पियरेटेन

आप अपने आवश्यक पैरामीटर कहाँ से प्राप्त कर रहे हैं? (config फ़ाइल, db, ??)
Jaime

runTimeParamएक निर्भरता है जो उपयोगकर्ता इनपुट के आधार पर रन-टाइम पर निर्धारित की जाती है। क्या इसके लिए विकल्प को दो इंटरफेस में विभाजित किया जाना चाहिए - एक आरंभीकरण के लिए और दूसरा भंडारण मूल्यों के लिए?
इगोर ज़ेवाका

IoC में निर्भरता, आमतौर पर अन्य रेफरी प्रकार की कक्षाओं या वस्तुओं के लिए निर्भरता को संदर्भित करता है जिसे IoC आरंभीकरण चरण में निर्धारित किया जा सकता है। यदि आपकी कक्षा को काम करने के लिए बस कुछ मूल्यों की आवश्यकता है, तो यहीं से आपकी कक्षा में प्रारंभिक () विधि काम में आती है।
द लाइट

मेरा मतलब है कि आपके ऐप में 100 कक्षाएं हैं, जिस पर यह दृष्टिकोण लागू किया जा सकता है; फिर आपको अपनी कक्षाओं के लिए अतिरिक्त 100 फ़ैक्टरी कक्षाएं + 100 इंटरफेस बनाने होंगे और यदि आप अभी आरम्भिक विधि () विधि का उपयोग कर रहे थे तो आप इससे दूर हो सकते हैं।
द लाइट

जवाबों:


276

किसी भी जगह जहां आपको किसी विशेष निर्भरता के निर्माण के लिए रन-टाइम मान की आवश्यकता होती है, एब्सट्रैक्ट फैक्ट्री इसका समाधान है।

इंटरफेस पर शुरुआती तरीकों से एक लीक एब्स्ट्रैक्शन की गंध आती है ।

आपके मामले में मैं कहूंगा कि आपको IMyIntfइंटरफ़ेस का उपयोग करना चाहिए कि आपको इसका उपयोग करने की आवश्यकता कैसे है - न कि आप इसके कार्यान्वयन को बनाने का इरादा कैसे करते हैं। यह एक कार्यान्वयन विवरण है।

इस प्रकार, इंटरफ़ेस बस होना चाहिए:

public interface IMyIntf
{
    string RunTimeParam { get; }
}

अब सार फैक्टरी को परिभाषित करें:

public interface IMyIntfFactory
{
    IMyIntf Create(string runTimeParam);
}

अब आप इसका एक ठोस कार्यान्वयन बना सकते हैं जो इस तरह के IMyIntfFactoryठोस उदाहरण बनाता है IMyIntf:

public class MyIntf : IMyIntf
{
    private readonly string runTimeParam;

    public MyIntf(string runTimeParam)
    {
        if(runTimeParam == null)
        {
            throw new ArgumentNullException("runTimeParam");
        }

        this.runTimeParam = runTimeParam;
    }

    public string RunTimeParam
    {
        get { return this.runTimeParam; }
    }
}

ध्यान दें कि यह किस प्रकार हमें readonlyकीवर्ड के उपयोग द्वारा वर्ग के आक्रमणकारियों को बचाने की अनुमति देता है । कोई बदबूदार प्रारंभिक तरीके आवश्यक नहीं हैं।

एक IMyIntfFactoryकार्यान्वयन इस रूप में सरल हो सकता है:

public class MyIntfFactory : IMyIntfFactory
{
    public IMyIntf Create(string runTimeParam)
    {
        return new MyIntf(runTimeParam);
    }
}

आपके सभी उपभोक्ताओं में जहां आपको एक IMyIntfउदाहरण की आवश्यकता होती है , आप बस IMyIntfFactoryइसे कन्स्ट्रक्टर इंजेक्शन के माध्यम से अनुरोध करके एक निर्भरता लेते हैं ।

IMyIntfFactoryयदि आप इसे सही तरीके से पंजीकृत करते हैं, तो इसके नमक के लायक कोई भी डीआई कंटेनर आपके लिए एक उदाहरण ऑटो-वायर में सक्षम होगा ।


13
समस्या यह है कि एक विधि (जैसे प्रारंभिक) आपके एपीआई का हिस्सा है, जबकि निर्माणकर्ता नहीं है। blog.ploeh.dk/2011/02/28/InterfacesAreAccessModifiers.aspx
मार्क

13
इसके अलावा, एक प्रारंभिक विधि टेम्पोरल कपलिंग को इंगित करती है: blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling.aspx
Mark Seemann

2
@ डर्लेन आप मेरी पुस्तक के खंड of.३.६ में वर्णित के अनुसार एक आलसी आरम्भिक सज्जाकार का उपयोग कर सकते हैं । मैं अपनी प्रस्तुति बिग ऑब्जेक्ट ग्राफ्स अप फ्रंट में भी कुछ इसी तरह का एक उदाहरण प्रदान करता हूं ।
मार्क सेमैन

2
@ मर्क यदि कारखाने के MyIntfकार्यान्वयन के निर्माण से अधिक की आवश्यकता होती है runTimeParam(पढ़ें: अन्य सेवाएं जिन्हें एक आईओसी द्वारा हल किया जाएगा), तो आपको अभी भी अपने कारखाने में उन निर्भरता को हल करने का सामना करना पड़ रहा है। मुझे इसे हल करने के लिए कारखाने के निर्माणकर्ता में उन निर्भरताओं को पारित करने का @PhilSandler जवाब पसंद है - क्या यह आपके साथ भी है?
जेफ

2
यह भी महान सामान है, लेकिन इस अन्य प्रश्न के लिए आपका जवाब वास्तव में मेरी बात के लिए मिला।
जेफ

15

आमतौर पर जब आप इस स्थिति का सामना करते हैं, तो आपको अपने डिजाइन को फिर से दिखाने और यह निर्धारित करने की आवश्यकता होती है कि क्या आप अपनी शुद्ध सेवाओं के साथ अपने स्टेटफुल / डेटा ऑब्जेक्ट्स को मिला रहे हैं। अधिकांश (सभी नहीं) मामलों में, आप इन दो प्रकार की वस्तुओं को अलग रखना चाहेंगे।

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

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


5

मैं इस स्थिति में भी आया हूं कि कुछ बार वातावरण में जहां मैं गतिशील रूप से मॉडल ऑब्जेक्ट्स के आधार पर ViewModel ऑब्जेक्ट बना रहा हूं (इस अन्य Stackoverflow पोस्ट द्वारा वास्तव में अच्छी तरह से उल्लिखित है )।

मुझे यह पसंद आया कि निन्यूज एक्सटेंशन जो आपको इंटरफेस के आधार पर कारखानों को गतिशील बनाने की अनुमति देता है:

Bind<IMyFactory>().ToFactory();

मुझे एकता में सीधे तौर पर कोई समान कार्यक्षमता नहीं मिली ; इसलिए मैंने IUnityContainer को अपना स्वयं का विस्तार लिखा है, जो आपको उन कारखानों को पंजीकृत करने की अनुमति देता है जो मौजूदा वस्तुओं से डेटा के आधार पर नई वस्तुओं का निर्माण करेंगे, जो अनिवार्य रूप से एक प्रकार के पदानुक्रम से एक अलग प्रकार के पदानुक्रम तक मैपिंग कर सकते हैं: यूनिटी मैपिंग फ़ाइटर

सादगी और पठनीयता के लक्ष्य के साथ, मैंने एक विस्तार के साथ समाप्त किया जो आपको व्यक्तिगत फैक्ट्री कक्षाओं या इंटरफेस (एक वास्तविक समय सेवर) की घोषणा किए बिना मैपिंग को सीधे निर्दिष्ट करने की अनुमति देता है। आप केवल उन मैपिंग को जोड़ते हैं जहां आप सामान्य बूटस्ट्रैपिंग प्रक्रिया के दौरान कक्षाएं पंजीकृत करते हैं ...

//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();

//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();

तो फिर तुम सिर्फ CI के लिए निर्माता में मानचित्रण कारखाने के इंटरफ़ेस की घोषणा करते हैं और इसकी बनाएँ () विधि का उपयोग करते हैं ...

public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }

public TextWidgetViewModel(ITextWidget widget) { }

public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
    IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
    foreach (IWidget w in data.Widgets)
        children.Add(factory.Create(w));
}

अतिरिक्त बोनस के रूप में, मैप किए गए वर्गों के निर्माण में कोई भी अतिरिक्त निर्भरता भी ऑब्जेक्ट निर्माण के दौरान हल हो जाएगी।

जाहिर है, यह हर समस्या को हल नहीं करेगा, लेकिन इसने मुझे बहुत अच्छी तरह से सेवा दी है, इसलिए मैंने सोचा कि मुझे इसे साझा करना चाहिए। GitHub पर परियोजना की साइट पर अधिक प्रलेखन है।


1

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

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

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

यहां तक ​​कि अगर आप निन्यूज का उपयोग नहीं करते हैं, तो वॉकथ्रू आपको कार्यक्षमता का अवधारणा और शब्दावली देगा जो आपके उद्देश्य के अनुरूप है, और आपको उस ज्ञान को एकता या अन्य डि फ्रेमवर्क में मैप करने में सक्षम होना चाहिए (या निनटेब को एक कोशिश देने के लिए आपको समझाने में सक्षम होना चाहिए) ।


उसके लिए धन्यवाद। मैं वास्तव में DI फ्रेमवर्क का मूल्यांकन कर रहा हूं और NInject मेरा अगला बनने जा रहा है।
इगोर ज़ेवाका

@johann: प्रदाताओं? github.com/ninject/ninject/wiki/...
एंथोनी

1

मुझे लगता है कि मैंने इसे हल कर दिया है और यह अच्छा लगता है, इसलिए यह आधा सही होना चाहिए :))

मैं IMyIntfएक "गेट्टर" और एक "सेटर" इंटरफेस में विभाजित हूं । इसलिए:

interface IMyIntf {
  string RunTimeParam { get; }
}


interface IMyIntfSetter {
  void Initialize(string runTimeParam);
  IMyIntf MyIntf {get; }
}

फिर कार्यान्वयन:

class MyIntfImpl : IMyIntf, IMyIntfSetter {
  string _runTimeParam;

  void Initialize(string runTimeParam) {
    _runTimeParam = runTimeParam;
  }

  string RunTimeParam { get; }

  IMyIntf MyIntf {get {return this;} }
}

//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;

IMyIntfSetter.Initialize()अभी भी कई बार कहा जा सकता है, लेकिन सर्विस लोकेटर प्रतिमान के बिट्स का उपयोग करके हम इसे काफी अच्छी तरह से लपेट सकते हैं, जो कि IMyIntfSetterलगभग एक आंतरिक इंटरफ़ेस है जो इससे अलग है IMyIntf


13
यह एक विशेष रूप से अच्छा समाधान नहीं है क्योंकि यह एक प्रारंभिक विधि पर निर्भर करता है, जो एक लीक एब्स्ट्रेक्शन है। Btw, यह सर्विस लोकेटर की तरह नहीं दिखता है, लेकिन इंटरफ़ेस इंजेक्शन की तरह अधिक है। किसी भी मामले में, बेहतर समाधान के लिए मेरा जवाब देखें।
मार्क सेमैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.