2012-04-25 15 views
24

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

यह एक साधारण उदाहरण है, लेकिन मान लीजिए कि मुझे ILogger को मेरी कक्षाओं में से एक में इंजेक्ट करने की आवश्यकता है।

public interface ILogger 
{ 
    void Info(string message); 
} 

public class Logger : ILogger 
{ 
    public void Info(string message) { } 
} 

इस तरह के 1 से 1 रिश्ते को कोड गंध की तरह लगता है। चूंकि मेरे पास केवल एक ही कार्यान्वयन है, यदि कोई कक्षा बनाते हैं और Info विधि को केवल एक वर्ग के लिए इंटरफ़ेस बनाने के बजाय मेरे परीक्षणों में ओवरराइड करने के लिए आभासी के रूप में चिह्नित करने के लिए कोई संभावित समस्याएं हैं?

public class SqlLogger : Logger 
{ 
    public override void Info(string message) 
    { 
     // Log to SQL 
    } 
} 

इन कक्षाओं में से प्रत्येक के विशिष्ट गुण या तरीकों कि एक टपकाया अमूर्त बनाने चाहते हैं, तो मैं एक आधार बाहर निकाल सकते:

public class Logger 
{ 
    public virtual void Info(string message) 
    { 
     // Log to file 
    } 
} 

अगर मैं एक और क्रियान्वयन की आवश्यकता नहीं है, मैं Info विधि ओवरराइड कर सकते हैं कक्षा:

public class Logger 
{ 
    public virtual void Info(string message) 
    { 
     throw new NotImplementedException(); 
    } 
} 

public class SqlLogger : Logger 
{ 
    public override void Info(string message) { } 
} 

public class FileLogger : Logger 
{ 
    public override void Info(string message) { } 
} 

कारण मैंने आधार वर्ग को सार के रूप में चिह्नित नहीं किया है क्योंकि अगर मैं कभी भी एक और विधि जोड़ना चाहता हूं, तो मैं exis तोड़ नहीं पाऊंगा टिंग कार्यान्वयन। उदाहरण के लिए, यदि मेरे FileLogger को Debug विधि की आवश्यकता है, तो मैं मौजूदा SqlLogger को तोड़ने के बिना बेस क्लास Logger अपडेट कर सकता हूं।

public class Logger 
{ 
    public virtual void Info(string message) 
    { 
     throw new NotImplementedException(); 
    } 

    public virtual void Debug(string message) 
    { 
     throw new NotImplementedException(); 
    } 
} 

public class SqlLogger : Logger 
{ 
    public override void Info(string message) { } 
} 

public class FileLogger : Logger 
{ 
    public override void Info(string message) { } 
    public override void Debug(string message) { } 
} 

फिर, यह एक साधारण उदाहरण है, लेकिन जब मुझे एक इंटरफ़ेस पसंद करना चाहिए?

+1

_ कारण मैंने आधार वर्ग को सार के रूप में चिह्नित नहीं किया है क्योंकि अगर मैं कभी भी एक और विधि जोड़ना चाहता हूं, तो एक अमूर्त वर्ग में कार्यान्वयन हो सकता है। आप अपने सार लॉगर वर्ग में डीबग विधि जोड़ सकते हैं। –

+0

मौजूदा कार्यान्वयन तोड़ना केवल एक समस्या है यदि आप पुन: प्रयोज्य लाइब्रेरी लिख रहे हैं। क्या आप? या आप बस व्यवसाय आवेदन की एक पंक्ति लिख रहे हैं? – Steven

+0

यह सवाल का दायरा नहीं है, लेकिन विरासत अतिरंजित है। एक 'SqlLogger'' SqlLogPersistenceStrategy' के साथ बस एक ठोस 'लॉगर' है। ज्यादातर मामलों में संरचना विरासत से काफी बेहतर है। आपकी समस्या के लिए, आईएसपी के बारे में क्या? 'ILogInfo',' ILogError', आदि – plalx

उत्तर

27

"त्वरित" उत्तर

मैं इंटरफेस के साथ छड़ी होगी। वे को बाहरी इकाइयों के लिए खपत के अनुबंध के लिए डिजाइन किए गए हैं।

@ जकूबकोनेकी ने कई विरासत का उल्लेख किया। मुझे लगता है कि यह इंटरफेस के साथ चिपकने का सबसे बड़ा कारण है क्योंकि उपभोक्ता पक्ष पर यह बहुत स्पष्ट हो जाएगा यदि आप उन्हें बेस क्लास लेने के लिए मजबूर करते हैं ... कोई भी बेस क्लास को उन पर जोर नहीं देता है।

अपडेट किया गया "त्वरित" उत्तर

आप अपने नियंत्रण से बाहर इंटरफेस कार्यान्वयन के साथ मुद्दों कहा है। एक अच्छा तरीका है कि पुराने से विरासत में एक नया इंटरफ़ेस बनाना और अपना कार्यान्वयन ठीक करना।फिर आप अन्य टीमों को सूचित कर सकते हैं कि एक नया इंटरफ़ेस उपलब्ध है। समय के साथ, आप पुराने इंटरफेस को हटा सकते हैं।

भूलें कि आप explicit interface implementations के समर्थन का उपयोग कर सकते हैं ताकि इंटरफेस के बीच एक अच्छा विभाजन हो सके जो कि तर्कसंगत रूप से समान है, लेकिन विभिन्न संस्करणों के बीच।

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

कार्यान्वयन बनाम खपत

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

एक विधि को हटाने से उपभोक्ता टूट जाता है, लेकिन कार्यान्वयन तोड़ता नहीं है - हालांकि आप अपने उपभोक्ताओं के लिए पीछे की संगतता के प्रति जागरूक होने पर ऐसा नहीं करेंगे।

मेरे अनुभव

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

हमारा इंटरफ़ेस-आधारित दृष्टिकोण अब हमें अच्छी स्थिति में खड़ा कर रहा है क्योंकि हम फैक्ट्री पैटर्न और डीआई के तत्वों जैसे तकनीकों का उपयोग वृद्ध विरासत कोड बेस को बेहतर बनाने के लिए करते हैं। परीक्षण तेजी से इस तथ्य का लाभ उठाने में सक्षम था कि "निश्चित" उपयोग (यानि, कंक्रीट कक्षाओं के साथ केवल 1-1 मैपिंग नहीं) खोजने से पहले कई वर्षों तक इंटरफेस कोड बेस में मौजूद थे।

बेस कक्षा विपक्ष

बेस कक्षाएं आम संस्थाओं के लिए कार्यान्वयन विवरण साझा करने के लिए कर रहे हैं, वास्तव में वे एक API सार्वजनिक रूप से साझा के साथ कुछ ऐसा ही कर पा रहे हैं मेरी राय में एक उप-उत्पाद है। इंटरफेस को सार्वजनिक रूप से एपीआई साझा करने के लिए डिज़ाइन किया गया है, इसलिए उनका उपयोग करें।

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

ब्रेकिंग/सहायक क्रियान्वयन

आप नीचे इंटरफ़ेस मार्ग आप कठिनाई में पड़ सकते हैं जाना अनुबंध तोड़ने की वजह से भी इंटरफ़ेस को बदलने हैं। साथ ही, जैसा कि आप उल्लेख करते हैं, आप अपने नियंत्रण के बाहर कार्यान्वयन तोड़ सकते हैं। इस समस्या से निपटने के दो तरीके हैं:

  1. राज्य कि आप उपभोक्ताओं को तोड़ नहीं देंगे, लेकिन आप कार्यान्वयन का समर्थन नहीं करेंगे।
  2. राज्य जो एक बार इंटरफेस प्रकाशित हो जाता है, यह कभी नहीं बदला जाता है।

मैं बाद देखा है, मैं इसे दो guises में आ देखें:

  1. किसी भी नए सामान के लिए पूरी तरह से अलग इंटरफेस: MyInterfaceV1, MyInterfaceV2
  2. इंटरफ़ेस विरासत: MyInterfaceV2 : MyInterfaceV1

मैं व्यक्तिगत रूप से इस मार्ग नीचे जाने के लिए का चयन नहीं हैं, मैं परिवर्तन को तोड़ने से कार्यान्वयन का समर्थन नहीं करने का निर्णय लेंगे। लेकिन कभी-कभी हमारे पास यह विकल्प नहीं होता है।

कुछ कोड

public interface IGetNames 
{ 
    List<string> GetNames(); 
} 

// One option is to redefine the entire interface and use 
// explicit interface implementations in your concrete classes. 
public interface IGetMoreNames 
{ 
    List<string> GetNames(); 
    List<string> GetMoreNames(); 
} 

// Another option is to inherit. 
public interface IGetMoreNames : IGetNames 
{ 
    List<string> GetMoreNames(); 
} 

// A final option is to only define new stuff. 
public interface IGetMoreNames 
{ 
    List<string> GetMoreNames(); 
} 
+0

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

+0

यदि आप इंटरफ़ेस नहीं बदल सकते हैं - तो आप एक और बना सकते हैं: 'इंटीफेस आईडीबगर {शून्य डीबग (स्ट्रिंग संदेश);}' और इसे फ़ाइल लॉगर में कार्यान्वित करें। इसलिए यदि अन्य टीमों को इसकी आवश्यकता नहीं है, तो वे इसका उपयोग नहीं करेंगे और इसे लागू नहीं करेंगे। –

+0

@nivlam इसका एक बार एक इंटरफेस बनाया गया है, इसे बदलने के लिए बंद कर दिया गया है। पुराने इंटरफेस बनाने वाले नए इंटरफेस बनाएं, फिर कार्यान्वयन वैकल्पिक रूप से नए इंटरफेस को भी कार्यान्वित कर सकते हैं ... जिसका अर्थ केवल आपका आंतरिक कार्यान्वयन है। मैंने सूट के लिए अपना जवाब अपडेट कर दिया है। –

2

आपको हमेशा इंटरफ़ेस पसंद करना चाहिए।

हां, कुछ मामलों में आपके पास कक्षा और इंटरफ़ेस पर समान विधियां होंगी, लेकिन अधिक जटिल परिदृश्यों में आप नहीं करेंगे। यह भी याद रखें कि .NET में कोई एकाधिक विरासत नहीं है।

आपको अपने इंटरफेस को एक अलग असेंबली में रखना चाहिए और आपकी कक्षाएं आंतरिक होनी चाहिए।

इंटरफेस के खिलाफ कोडिंग का एक और लाभ यूनिट परीक्षणों में उन्हें आसानी से नकल करने की क्षमता है।

+0

इंटरफेस को एक अलग असेंबली में रखना क्यों अच्छी बात है? – thedev

+4

@thedev आप कार्यान्वयन को प्रकाशित किए बिना इंटरफेस प्रकाशित कर सकते हैं।इन्हें अवांछित कार्यान्वयन को लीक किए बिना साझा किया जा सकता है जो केवल ओवरहेड या रखरखाव के मुद्दों को जोड़ता है। –

0

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

आगे, एडम होल्ड्सवर्थ का अनुबंध अनुबंध कोण बहुत रचनात्मक है। आईएमएचओ यह इंटरफेस के 1-1 कार्यान्वयन से कोड क्लीनर बनाता है इसे गंध बना देता है।

9

आपका ILogger इंटरफ़ेस interface segregation principle टूट रहा है जब आप Info अलावा Debug, Error, और Critical विधियां जोड़ने शुरू करते हैं। horrible Log4Net ILog interface पर एक नज़र डालें और आपको पता चलेगा कि मैं किस बारे में बात कर रहा हूं। ,

void Log(LogEntry entry); 

यह पूरी तरह से अपनी समस्याओं के सभी हल करती है क्योंकि::

इसके बजाय लॉग गंभीरता प्रति एक विधि बनाने के बजाय, एक भी विधि है कि एक लॉग वस्तु लेता बनाने

  1. LogEntry एक हो जाएगा सरल डीटीओ और आप किसी भी ग्राहक को तोड़ने के बिना इसमें नए गुण जोड़ सकते हैं।
  2. आप अपने ILogger इंटरफ़ेस के लिए एक्सटेंशन विधियों का एक सेट बना सकते हैं जो उस एकल Log विधि पर मानचित्र लगाते हैं।

    public static class LoggerExtensions 
    { 
        public static void Debug(this ILogger logger, string message) 
        { 
         logger.Log(new LogEntry(message) 
         { 
          Severity = LoggingSeverity.Debug, 
         }); 
        } 
    
        public static void Info(this ILogger logger, string message) 
        { 
         logger.Log(new LogEntry(message) 
         { 
          Severity = LoggingSeverity.Information, 
         }); 
        } 
    } 
    

    इस डिजाइन पर अधिक विस्तृत चर्चा के लिए, कृपया this पढ़ें:

यहां इस तरह के विस्तार विधि का एक उदाहरण है।

+0

समस्या अब यह है कि अनुबंध परिभाषित नहीं करता है कि गंभीरता स्तर क्या है या समर्थित नहीं है। आईएसपी के दिमाग में, 'ILogInfo',' ILogError', 'ILogDebug', आदि के बारे में क्या? – plalx

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