2012-02-06 13 views
30

मुझे कन्स्ट्रक्टर आधारित निर्भरता इंजेक्शन पसंद नहीं है।निर्भरता इंजेक्शन के लिए एक व्यवहार्य विकल्प?

मेरा मानना ​​है कि यह कोड जटिलता बढ़ाता है और रखरखाव को कम करता है, और मैं जानना चाहता हूं कि क्या कोई व्यवहार्य विकल्प हैं या नहीं।

मैं इंटरफ़ेस से कार्यान्वयन को अलग करने की अवधारणा के बारे में बात नहीं कर रहा हूं, और इंटरफ़ेस से ऑब्जेक्ट्स के सेट को गतिशील रूप से हल करने का एक तरीका है (रिकर्सिव)। मैं पूरी तरह से इसका समर्थन करता हूं। हालांकि, ऐसा करने के पारंपरिक कन्स्ट्रक्टर आधारित तरीके में कुछ समस्याएं हैं।

1) सभी परीक्षण रचनाकारों पर निर्भर करते हैं।

पिछले वर्ष की तुलना एक MVC 3 सी # परियोजना में बड़े पैमाने पर डि इस्तेमाल करने के बाद, मैं इस तरह हमारे कोड चीजों से भरा लगता है:

public interface IMyClass { 
    ... 
} 

public class MyClass : IMyClass { 

    public MyClass(ILogService log, IDataService data, IBlahService blah) { 
    _log = log; 
    _blah = blah; 
    _data = data; 
    } 

    ... 
} 

समस्या: मैं अपने कार्यान्वयन में किसी अन्य सेवा की जरूरत है मैं को संशोधित करना कन्स्ट्रक्टर; और इसका मतलब है कि इस वर्ग के ब्रेक के लिए सभी यूनिट परीक्षण।

यहां तक ​​कि जिन परीक्षणों के पास नई कार्यक्षमता के साथ कुछ भी नहीं है, कम से कम अतिरिक्त पैरामीटर जोड़ने और उस तर्क के लिए एक नकली इंजेक्शन की आवश्यकता होती है।

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

2) सेवा उदाहरण अनावश्यक रूप से, कोड जटिलता में वृद्धि के आसपास पारित हो जाते हैं।

public class MyWorker { 
    public MyWorker(int x, IMyClass service, ILogService logs) { 
    ...  
    } 
} 

अगर तभी संभव है अगर मैं एक संदर्भ में, जहां दिए गए सेवाएं उपलब्ध हैं और स्वचालित रूप से हल किया गया है (उदाहरण के लिए। नियंत्रक) या, दुख, कई चेन सेवा उदाहरण नीचे पारित करके अंदर हूँ इस वर्ग का एक उदाहरण बनाना सहायक वर्गों का।

मैं इस की तरह कोड हर समय देखें:

public class BlahHelper { 

    // Keep so we can create objects later 
    var _service = null; 

    public BlahHelper(IMyClass service) { 
    _service = service; 
    } 

    public void DoSomething() { 
    var worker = new SpecialPurposeWorker("flag", 100, service); 
    var status = worker.DoSomethingElse(); 
    ... 

    } 
} 

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

यदि कोई कक्षा किसी सेवा पर निर्भर नहीं है, तो उसे उस सेवा पर निर्भरता होनी चाहिए। यह विचार है कि एक 'क्षणिक' निर्भरता है जहां एक वर्ग सेवा का उपयोग नहीं करता है, लेकिन मेरी राय में, बस इसे पास करता है।

हालांकि, मुझे एक बेहतर समाधान के बारे में पता नहीं है।

क्या कोई ऐसी चीज है जो इन समस्याओं के बिना DI के लाभ प्रदान करती है?

public MyClass() { 
    _log = Register.Get().Resolve<ILogService>(); 
    _blah = Register.Get().Resolve<IBlahService>(); 
    _data = Register.Get().Resolve<IDataService>(); 
} 

वहाँ ऐसा करने के लिए किसी भी नकारात्मक पक्ष यह है:

मैं कंस्ट्रक्टर्स अंदर डि ढांचे का उपयोग कर के बजाय के रूप में इस का समाधान करता है समस्याओं के एक जोड़े पर विचार किया है?

इसका मतलब है कि यूनिट परीक्षणों में परीक्षण के दौरान सही प्रकार के लिए मोक्स बांधने के लिए कक्षा के 'पूर्व ज्ञान' होना चाहिए, लेकिन मैं किसी अन्य डाउनसाइड्स को नहीं देख सकता।

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

+0

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

उत्तर

17

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

सभी परीक्षणों कंस्ट्रक्टर्स पर निर्भर करते हैं:

अपने बयान के माध्यम से चलते हैं।

[स्निप]

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

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

2) सेवा उदाहरण अनावश्यक रूप से, कोड जटिलता में वृद्धि के आसपास पारित हो जाते हैं।

डीआई के सबसे सामान्य उपयोगों में से एक व्यापार तर्क से वस्तु निर्माण को अलग करना है। आपके मामले में हम देखते हैं कि आपको वास्तव में जो कुछ चाहिए वह कुछ श्रमिक बनाने के लिए है जो बदले में कई निर्भरताओं की आवश्यकता है जो पूरे ऑब्जेक्ट ग्राफ़ के माध्यम से इंजेक्शन दिए जाते हैं। इस समस्या को हल करने का सबसे अच्छा तरीका व्यापार तर्क में new एस कभी भी नहीं करना है। ऐसे मामलों के लिए मैं कार्यकर्ता के वास्तविक निर्माण से व्यवसाय कोड को समेकित करने वाले कार्यकर्ता कारखाने को इंजेक्ट करना पसंद करूंगा।

public MyClass() { 
    _log = Register.Get().Resolve<ILogService>(); 
    _blah = Register.Get().Resolve<IBlahService>(); 
    _data = Register.Get().Resolve<IDataService>(); 
} 

वहाँ ऐसा करने के लिए किसी भी नकारात्मक पक्ष यह है:

मैं कंस्ट्रक्टर्स अंदर डि ढांचे का उपयोग कर के बजाय के रूप में इस का समाधान करता है समस्याओं के एक जोड़े पर विचार किया है?

एक लाभ आप Singleton पैटर्न (untestable कोड और अपने आवेदन की एक बड़ी राज्य अंतरिक्ष) का उपयोग कर के सभी कमियां मिल जाएगा के रूप में।

तो मैं कहूंगा कि DI सही किया जाना चाहिए (किसी भी अन्य उपकरण के रूप में)। आपकी समस्याओं का समाधान (आईएमओ) डीआई और आपकी टीम के सदस्यों की शिक्षा को समझने में निहित है।

+4

'रजिस्टर। गेट()। हल करें <>() '[सेवा लोकेटर एंटी-पैटर्न] (http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx) का एक आदर्श उदाहरण है। यदि सेवा परीक्षण के लिए एक और निर्भरता जोड़ना है तो कई परीक्षण मामलों को तोड़ने के लिए आपको अपने परीक्षणों को दोबारा करना चाहिए। जैसा कि @ एलेक्स ने सुझाव दिया कि उन्हें एसआरपी का पालन करना एक बात है। एक और [testInitializeAttribute] जैसे कुछ उपयोग करना होगा (http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.testinitializeattribute.aspx) (यदि आप एमएसटेस्ट का उपयोग कर रहे हैं) को रखने के लिए एक ही स्थान पर अपने परीक्षण का मुख्य सेटअप। –

+0

सहायक। "अधिक शिक्षित हो"। यह स्वीकार्य उत्तर कैसा है? –

13

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

आइए प्रत्येक स्पष्ट समस्या को अलग-अलग देखें।

सभी परीक्षण रचनाकारों पर निर्भर करते हैं।

समस्या यह है कि यूनिट परीक्षण कंसल्टर्स को कसकर जोड़ दिया गया है। इसे अक्सर एक सरल SUT Factory के साथ उपचार किया जा सकता है - एक अवधारणा जिसे Auto-mocking Container में बढ़ाया जा सकता है।

किसी भी मामले में जब कन्स्ट्रक्टर इंजेक्शन, constructors should be simple का उपयोग करते हैं तो उन्हें सीधे जांचने का कोई कारण नहीं है। वे कार्यान्वयन विवरण हैं जो आपके द्वारा लिखे गए व्यवहार परीक्षणों के दुष्प्रभाव के रूप में होते हैं।

सेवा उदाहरण अनावश्यक रूप से, कोड जटिलता में वृद्धि के आसपास पारित हो जाते हैं।

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

जब ऐसा होता है तो यह एक लक्षण है कि Facade Service गुम है। इसके बजाय ऐसा करने का:

public class BlahHelper { 

    // Keep so we can create objects later 
    var _service = null; 

    public BlahHelper(IMyClass service) { 
     _service = service; 
    } 

    public void DoSomething() { 
     var worker = new SpecialPurposeWorker("flag", 100, service); 
     var status = worker.DoSomethingElse(); 
     // ... 

    } 
} 

ऐसा करते हैं:

public class BlahHelper { 
    private readonly ISpecialPurposeWorkerFactory factory; 

    public BlahHelper(ISpecialPurposeWorkerFactory factory) { 
     this.factory = factory; 
    } 

    public void DoSomething() { 
     var worker = this.factory.Create("flag", 100); 
     var status = worker.DoSomethingElse(); 
     // ... 

    } 
} 

प्रस्तावित समाधान

समाधान प्रस्तावित एक सेवा लोकेटर, और it has only disadvantages and no benefits है के बारे में।

+0

और लॉगिंग सेवा के बारे में क्या? यह एक समस्या है जिसे हर वर्ग में इंजेक्शन दिया जा रहा है ... – danfromisrael

+2

लॉगिंग एक सेवा नहीं है - यह एक क्रॉस-काटने की चिंता है: http://stackoverflow.com/questions/7905110/logging-aspect-oriented-programming-and- निर्भरता -इजेक्शन-कोशिश करने के लिए बनाने/7906547 # 7906547 –

+0

मुझे आपके समाधान पसंद हैं, लेकिन ऐसा लगता है कि DI की बजाय फैक्ट्री पैटर्न का उपयोग करने का समर्थन करता है; मैं इसके साथ ठीक हूं, लेकिन क्या आप विस्तार कर सकते हैं कि यह बेहतर क्यों है? डाउनसाइड्स (उदाहरण के लिए यहां शामिल हैं: http://code.google.com/p/google-guice/wiki/Motivation) फैक्ट्री पैटर्न के साथ जो लोग पहले कवर कर चुके हैं। – Doug

संबंधित मुद्दे