एक्सटेंशन इंटरफ़ेस पैटर्न

.NET 3.5 में नए एक्सटेंशन इंटरफ़ेस से कार्यक्षमता को विभाजित करने की अनुमति देते हैं।

उदाहरण के लिए .NET 2.0 में

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }

    List GetChildren()
}

कर सकते हैं (3.5 में) बनें:

public interface IHaveChildren {
    string ParentType { get; }
    int ParentId { get; }
}

public static class HaveChildrenExtension {
    public static List GetChildren( this IHaveChildren ) {
        //logic to get children by parent type and id
        //shared for all classes implementing IHaveChildren 
    }
}

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

एकमात्र नुकसान यह है कि एक अमूर्त आधार कार्यान्वयन आभासी हो सकता है, लेकिन क्या यह आसपास काम किया जा सकता है (क्या एक उदाहरण विधि एक ही नाम के साथ एक विस्तार विधि को छुपाएगी? क्या यह ऐसा करने के लिए भ्रमित कोड होगा?)

नियमित रूप से इस पैटर्न का उपयोग न करने के किसी भी अन्य कारण?


स्पष्टीकरण:

हाँ, मुझे लगता है कि एक्सटेंशन विधियों के साथ प्रवृत्ति हर जगह उनके साथ खत्म होना है। मैं विशेष रूप से सावधान रहूंगा। नेट मूल्य प्रकारों के बिना पीयर समीक्षा के बहुत सारे (मुझे लगता है कि स्ट्रिंग पर हमारे पास केवल एक ही है .SplitToDictionary() - के समान। विभाजन() लेकिन एक कुंजी-मूल्य डिलीमीटर भी लेना)

मुझे लगता है कि वहां एक पूरी तरह से सर्वश्रेष्ठ अभ्यास बहस है ;-)

(संयोग से: डैनीसमुर्फ, आपका पीएम डरावना लगता है।)

मैं विशेष रूप से विस्तार विधियों का उपयोग करने के बारे में यहां पूछ रहा हूं जहां पहले हमारे पास इंटरफ़ेस विधियां थीं।


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

क्या एमएस ने आईएनयूमेरेबल और लिंक के लिए IQueryable किया है?

0
ro fr bn

11 उत्तर

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

एक बार जब कोई अन्य ऑब्जेक्ट विस्तारित एक का उपयोग करने का प्रयास करता है, तो वे एक्सटेंशन नहीं देख पाएंगे और उन्हें फिर से दोबारा दोबारा संदर्भित करना होगा।

पारंपरिक ज्ञान यह है कि एक्सटेंशन विधियों का केवल उपयोग किया जाना चाहिए:

  • उपयोगिता वर्ग, जैसा वैभव ने उल्लेख किया है
  • मुहरबंद तृतीय पक्ष एपीआई विस्तारित
0
जोड़ा

मुझे लगता है कि एक्सटेंशन विधियों की जगह सबसे अच्छी बात यह है कि आप उन सभी उपयोगिता वर्गों को प्राप्त करते हैं जो आपको हर प्रोजेक्ट में मिलती हैं।

कम से कम अब के लिए, मुझे लगता है कि एक्सटेंशन विधियों का कोई अन्य उपयोग कार्यस्थल में भ्रम पैदा करेगा।

मेरे दो बिट्स

0
जोड़ा

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

यही कारण है कि आप हमेशा इंटरफेस को वास्तविक वर्ग के बजाय प्रकार के रूप में घोषित करते हैं।

IInterface variable = new ImplementingClass();

सही?

यदि आपको वास्तव में कुछ अतिरिक्त कार्यक्षमता के साथ अनुबंध की आवश्यकता है, तो अमूर्त कक्षाएं आपके मित्र हैं।

0
जोड़ा

I needed to solve something similar: I wanted to have a List passed to the extensions function where IIDable is an interface that has a long getId() function. I tried using GetIds(this List bla) but the compiler didn't allow me to do so. I used templates instead and then type casted inside the function to the interface type. I needed this function for some linq to sql generated classes.

आशा है कि ये आपकी मदद करेगा :)

    public static List GetIds(this List original){
        List ret = new List();
        if (original == null)
            return ret;

        try
        {
            foreach (T t in original)
            {
                IIDable idable = (IIDable)t;
                ret.Add(idable.getId());
            }
            return ret;
        }
        catch (Exception)
        {
            throw new Exception("Class calling this extension must implement IIDable interface");
        }
0
जोड़ा

रॉब कॉनरी (सबसनिक और एमवीसी स्टोरफ्रंट) ने अपने स्टोरफ्रंट एप्लिकेशन में एक आईरिपोजिटरी-जैसी पैटर्न लागू की। यह उपरोक्त पैटर्न नहीं है, लेकिन यह कुछ समानताएं साझा करता है।

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

पारंपरिक दृष्टिकोण नहीं, लेकिन बहुत अच्छा और निश्चित रूप से DRY का मामला है।

0
जोड़ा

इंटरफेस को विस्तारित करने में कुछ भी गलत नहीं है, वास्तव में यह है कि LINQ संग्रह वर्गों में एक्सटेंशन विधियों को जोड़ने के लिए कैसे काम करता है।

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

0
जोड़ा

मैं एक अच्छी चीज के रूप में एक्सटेंशन विधियों का उपयोग कर डोमेन/मॉडल और यूआई/कार्यक्षमता को अलग करना देखता हूं, खासकर जब से वे अलग-अलग नामस्थानों में रह सकते हैं।

उदाहरण के लिए:

namespace Model
{
    class Person
    {
        public string Title { get; set; }
        public string FirstName { get; set; }
        public string Surname { get; set; }
    }
}

namespace View
{
    static class PersonExtensions
    {
        public static string FullName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName + " " + p.Surname;
        }

        public static string FormalName(this Model.Person p)
        {
            return p.Title + " " + p.FirstName[0] + ". " + p.Surname;
        }
    }
}

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

0
जोड़ा
मुझे यह दृष्टिकोण ज़ूबा भी पसंद है, हालांकि मैं आंशिक वर्ग की तर्ज पर गया, जहां आंशिक सभी "एक्सटेंशन" आयोजित किए गए।
जोड़ा लेखक Matthew Zielonka.co.uk, स्रोत

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

यहां तक ​​कि अपने काम पर भी, मैं यह देख सकता था कि मेरे पीएम की प्रत्येक कोड को जोड़ने की इच्छा है जिसे हम यूआई या डेटा एक्सेस लेयर पर काम करते हैं, मैं उसे पूरी तरह से 20 या 30 तरीकों से जोड़ने के लिए जोर दे सकता हूं System.String जो स्ट्रिंग हैंडलिंग से केवल स्पर्श-संबंधित हैं।

0
जोड़ा

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

प्रश्न के लिए: ऐसा करने पर सीमाओं से सावधान रहें - उदाहरण के लिए, दिखाए गए कोड में, GetChildren को प्रभावी ढंग से 'सील' लागू करने के लिए एक विस्तार विधि का उपयोग करके और यदि आवश्यक हो तो किसी भी IHaveChildren impl को स्वयं प्रदान करने की अनुमति नहीं देता है। यदि यह ठीक है, तो मुझे विस्तार विधि दृष्टिकोण बहुत अधिक नहीं लगता है। यह पत्थर में स्थापित नहीं है, और आमतौर पर अधिक लचीलापन की आवश्यकता होने पर आमतौर पर आसानी से प्रतिक्रिया की जा सकती है।

अधिक लचीलापन के लिए, रणनीति पैटर्न का उपयोग करना बेहतर हो सकता है। कुछ इस तरह:

public interface IHaveChildren 
{
    string ParentType { get; }
    int ParentId { get; }
}

public interface IChildIterator
{
    IEnumerable GetChildren();
}

public void DefaultChildIterator : IChildIterator
{
    private readonly IHaveChildren _parent;

    public DefaultChildIterator(IHaveChildren parent)
    {
        _parent = parent; 
    }

    public IEnumerable GetChildren() 
    { 
       //default child iterator impl
    }
}

public class Node : IHaveChildren, IChildIterator
{ 
   //*snip*

    public IEnumerable GetChildren()
    {
        return new DefaultChildIterator(this).GetChildren();
    }
}
0
जोड़ा

मुझे लगता है कि विस्तार विधियों के न्यायसंगत उपयोग ने (सार) आधार वर्गों के साथ एक और अधिक समेकित स्थिति पर इंटरफेस लगाए।


Versioning. One advantage base classes have over interfaces is that you can easily add new virtual members in a later version, whereas adding members to an interface will break implementers built against the old version of the library. Instead, a new version of the interface with the new members needs to be created, and the library will have to work around or limit access to legacy objects only implementing the original interface.

एक ठोस उदाहरण के रूप में, लाइब्रेरी का पहला संस्करण इस प्रकार एक इंटरफ़ेस परिभाषित कर सकता है:

public interface INode {
  INode Root { get; }
  List GetChildren( );
}

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

public interface IChildNode : INode {
  INode Parent { get; }
}

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

public static class NodeExtensions {
  public INode GetParent( this INode node ) {
   //If the node implements the new interface, call it directly.
    var childNode = node as IChildNode;
    if( !object.ReferenceEquals( childNode, null ) )
      return childNode.Parent;

   //Otherwise, fall back on a default implementation.
    return FindParent( node, node.Root );
  }
}

अब नई पुस्तकालय के सभी उपयोगकर्ता विरासत और आधुनिक कार्यान्वयन दोनों को समान रूप से इलाज कर सकते हैं।


Overloads. Another area where extension methods can be useful is in providing overloads for interface methods. You might have a method with several parameters to control its action, of which only the first one or two are important in the 90% case. Since C# does not allow setting default values for parameters, users either have to call the fully parameterized method every time, or every implementation must implement the trivial overloads for the core method.

इसके बजाय विस्तार विधियों का उपयोग छोटे अधिभार कार्यान्वयन प्रदान करने के लिए किया जा सकता है:

public interface ILongMethod {
  public bool LongMethod( string s, double d, int i, object o, ... );
}

...
public static LongMethodExtensions {
  public bool LongMethod( this ILongMethod lm, string s, double d ) {
    lm.LongMethod( s, d, 0, null );
  }
  ...
}


Please note that both of these cases are written in terms of the operations provided by the interfaces, and involve trivial or well-known default implementations. That said, you can only inherit from a class once, and the targeted use of extension methods can provide a valuable way to deal with some of the niceties provided by base classes that interfaces lack :)


Edit: A related post by Joe Duffy: Extension methods as default interface method implementations

0
जोड़ा

थोड़ा और।

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

((IFirst)this).AmbigousMethod()
0
जोड़ा
सहमत हैं, हालांकि यदि आपको ऐसा करना है तो मैं सुझाव देता हूं कि या तो इंटरफ़ेस खराब तरीके से डिज़ाइन किए गए हों या आपकी कक्षा दोनों को लागू नहीं करनी चाहिए।
जोड़ा लेखक Keith, स्रोत