2012-05-24 16 views
8

के बजाय थ्रेडस्टैटिक का उपयोग करने में क्या गड़बड़ है, मैं एक बहुत बड़ी, बहुत विरासत परियोजना टेस्ट करने योग्य बनाने की कोशिश कर रहा हूं।डीआई कंटेनर

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

class ServiceEveryoneNeeds 
{ 
    public static IImplementation _implementation = new RealImplementation(); 

    public IEnumerable<FooBar> GetAllTheThings() { return _implementation.GetAllTheThings(); } 
} 

अब मेरी इकाई परीक्षण में:

void MyTest() 
{ 
    ServiceEveryoneNeeds._implementation = new MockImplementation(); 
} 

अब तक तो अच्छा। प्रोड में, हमें केवल एक कार्यान्वयन की आवश्यकता है। लेकिन परीक्षण समानांतर में चलाने के लिए और अलग अलग mocks आवश्यकता हो सकती है, तो मैं ऐसा किया:

class Dependencies 
{ 
    //set this in prod to the real impl 
    public static IImplementation _realImplementation; 

    //unit tests set these 
    [ThreadStatic] 
    public static IImplementation _mock; 

    public static IImplementation TheImplementation 
    { get {return _realImplementation ?? _mock; } } 

    public static void Cleanup() { _mock = null; } 
} 

और फिर:

class ServiceEveryoneNeeds 
{ 
    static IImplementation GetImpl() { return Dependencies.TheImplementation; } 

    public static IEnumerable<FooBar> GetAllTheThings() {return GetImpl().GetAllTheThings(); } 

} 

//and 
void MyTest() 
{ 
    Dependencies._mock = new BestMockEver(); 
    //test 
    Dependencies.Cleanup(); 
} 

हम इस मार्ग है क्योंकि यह निर्माता के लिए हर वर्ग में इन सेवाओं इंजेक्षन एक बड़े पैमाने पर परियोजना है ले लिया उन्हें उनकी जरूरत है। साथ ही, ये हमारे कोडबेस के भीतर सार्वभौमिक सेवाएं हैं जिन पर अधिकांश कार्य निर्भर करते हैं।

मैं समझता हूं कि यह पैटर्न इस अर्थ में बुरा है कि यह निर्भरता को छुपाता है, क्योंकि कन्स्ट्रक्टर इंजेक्शन के विपरीत निर्भरता स्पष्ट करता है।

हालांकि लाभ हैं:
- हम तुरंत यूनिट परीक्षण शुरू कर सकते हैं, 3 महीने के रिएक्टर और फिर यूनिट परीक्षण कर सकते हैं।
- हमारे पास अभी भी ग्लोबल्स हैं, लेकिन यह हम कहां से सख्ती से बेहतर प्रतीत होते हैं।

जबकि हमारी निर्भरता अभी भी निहित है, मैं तर्क दूंगा कि यह दृष्टिकोण हमारे पास जो भी था उससे सख्ती से बेहतर है। छिपी निर्भरताओं के अलावा, क्या यह उचित डी कंटेनर का उपयोग करने से किसी भी तरह से खराब है? मैं किस समस्या में भागूँगा?

उत्तर

4

इसका service locator which is bad है। लेकिन आप को यह पहले से ही पता है। यदि आपका कोड बेस वह विशाल है, तो आंशिक माइग्रेशन क्यों शुरू नहीं करें? कंटेनर के साथ सिंगलटन उदाहरण पंजीकृत करें और जब भी आप अपने कोड में किसी वर्ग को स्पर्श करते हैं तो उन्हें इंजेक्शन देना प्रारंभ करें। फिर आप अधिकतर हिस्सों को (उम्मीदवार) काम करने की स्थिति में छोड़ सकते हैं और हर जगह DI के लाभ प्राप्त कर सकते हैं।

आदर्श रूप से DI के बिना भागों को समय के साथ घटाना चाहिए। और आप तुरंत परीक्षण शुरू कर सकते हैं।

+3

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

+1

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

1

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

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

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

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

4

इसे ambient context कहा जाता है। यदि उपयोग किया जाता है और सही तरीके से लागू किया गया है तो परिवेश संदर्भ का उपयोग करने में कुछ भी गलत नहीं है। वहाँ कुछ पूर्व शर्त है जब एक परिवेश संदर्भ इस्तेमाल किया जा सकता हैं:

  1. यह एक क्रॉस कटिंग चिंता यह है कि कुछ मूल्य
  2. आप एक स्थानीय डिफ़ॉल्ट
  3. आप यह सुनिश्चित करें कि null नहीं किया जा सकता करना है की जरूरत है देता है होना चाहिए सौंपा। (Null implementation का उपयोग करें)

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

आपके कार्यान्वयन में कई समस्याएं हैं (हालांकि शून्य, नामकरण, कोई डिफ़ॉल्ट निर्दिष्ट नहीं रोकता है)। आप निम्न तरीके से इसे लागू कर सकता है:

public class SomeCrossCuttingConcern 
{ 
    private static ISomeCrossCuttingConcern default = new DefaultSomeCrossCuttingConcern(); 

    [ThreadStatic] 
    private static ISomeCrossCuttingConcern current; 

    public static ISomeCrossCuttingConcern Default 
    { 
     get { return default; } 
     set 
     { 
      if (value == null) 
       throw new ArgumentNullException(); 
      default = value; 
     } 
    } 

    public static ISomeCrossCuttingConcern Current 
    { 
     get 
     { 
      if (current == null) 
       current = default; 
      return current; 
     } 

     set 
     { 
      if (value == null) 
       throw new ArgumentNullException(); 
      current = value; 
     } 
    } 

    public static void ResetToDefault() { current = null; } 
} 

एक परिवेश संदर्भ कि लाभ यह है कि आप क्रॉस कटिंग चिंताओं के लिए अपने एपीआई अपवित्र नहीं है।

लेकिन दूसरी ओर परीक्षण के परीक्षण के संबंध में आप आश्रित हो सकते हैं। जैसे यदि आप एक परीक्षण के लिए अपना मॉक सेट करना भूल जाते हैं तो यह ठीक से चलता है अगर नकली पहले किसी अन्य परीक्षण द्वारा स्थापित किया गया था। लेकिन जब यह स्टैंडअलोन चलाया जाता है या एक अलग क्रम में यह असफल हो जाता है। यह परीक्षण को और अधिक कठिन बनाता है।

1

मुझे लगता है कि आप जो कर रहे हैं वह बुरा नहीं है। आप अपना कोड बेस टेस्टेबल बनाने की कोशिश कर रहे हैं, और यह चाल छोटे चरणों में करना है। Working Effectively With Legacy Code पढ़ने पर आपको यह वही सलाह मिल जाएगी। हालांकि, आप जो भी कर रहे हैं उसके नकारात्मक पक्ष यह है कि एक बार जब आप डिप्डेंसी इंजेक्शन का उपयोग करना शुरू कर देते हैं, तो आपको फिर से अपना कोड बेस दोबारा बदलना होगा। लेकिन सबसे महत्वपूर्ण बात यह है कि आपको बहुत सारे टेस्ट कोड बदलना होगा।

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

मैं वर्तमान में एक विरासत कोड आधार पर काम कर रहा हूं और एक डी कंटेनर (दर्द) का उपयोग नहीं कर सकता। फिर भी मैं कन्स्ट्रक्टर इंजेक्शन का उपयोग करता हूं जहां मैं कर सकता हूं, जिसका कभी-कभी मतलब है कि मुझे कुछ प्रकारों पर poor mans dependency injection का उपयोग करने के लिए वापस जाना होगा। यह वह चाल है जिसका उपयोग मैं 'कन्स्ट्रक्टर इंजेक्शन बबल' को रोकने के लिए करता हूं। फिर भी, यह एक परिवेश संदर्भ का उपयोग करने से काफी बेहतर है। गरीब आदमी की डी उप इष्टतम है, लेकिन फिर भी आपको उचित यूनिट परीक्षण लिखने की अनुमति मिलती है और बाद में उस डिफ़ॉल्ट कन्स्ट्रक्टर को तोड़ने के बाद इसे और अधिक आसान बनाता है।

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