सामान्य इंटरफ़ेस बनाम जेनरिक?


20

मुझे याद नहीं है कि मैंने पिछली बार कब जेनेरिक क्लास लिखी थी। हर बार मुझे लगता है कि मुझे कुछ सोचने के बाद यह निष्कर्ष निकालना चाहिए कि मैं ऐसा नहीं करता।

इस प्रश्न के दूसरे उत्तर ने मुझे स्पष्टीकरण के लिए पूछा (क्योंकि मैं अभी तक टिप्पणी नहीं कर सकता, मैंने एक नया प्रश्न बनाया है)।

तो आइये दिए गए कोड को उस मामले के उदाहरण के रूप में लेते हैं जहाँ किसी को जेनरिक की आवश्यकता होती है:

public class Repository<T> where T : class, IBusinessOBject
{
  T Get(int id)
  void Save(T obj);
  void Delete(T obj);
}

इसमें प्रकार की बाधाएँ हैं: IBusinessObject

मेरे विचार का सामान्य तरीका है: कक्षा का उपयोग करने के लिए विवश है IBusinessObject, इसलिए इस Repositoryका उपयोग करने वाले वर्ग हैं। रिपॉजिटरी इन IBusinessObjectएस को स्टोर करती है , इस बात की सबसे अधिक संभावना ग्राहक इंटरफेस के Repositoryमाध्यम से वस्तुओं को प्राप्त करना और उनका उपयोग करना चाहते हैं IBusinessObject। तो सिर्फ क्यों नहीं

public class Repository
{
  IBusinessOBject Get(int id)
  void Save(IBusinessOBject obj);
  void Delete(IBusinessOBject obj);
}

थो, उदाहरण अच्छा नहीं है, क्योंकि यह सिर्फ एक और संग्रह प्रकार है और सामान्य संग्रह क्लासिक्स है। इस मामले में प्रकार की बाधा भी अजीब लगती है।

वास्तव में उदाहरण मेरे class Repository<T> where T : class, IBusinessbBjectलिए बहुत समान दिखता है class BusinessObjectRepository। कौन सी चीज है जेनेरिक को ठीक करने के लिए बनाया गया है।

पूरी बात यह है: कुछ भी संग्रह के अलावा सामान्य के लिए अच्छा है और बाधाओं टाइप सामान्य के रूप में विशेष के रूप में सामान्य नहीं बनाते हैं, वर्ग के अंदर सामान्य प्रकार पैरामीटर के बजाय इस प्रकार की बाधा का उपयोग करता है?

जवाबों:


24

चलो पहले शुद्ध पैरामीट्रिक बहुरूपता के बारे में बात करते हैं और बाद में बंधे बहुरूपता में प्रवेश करते हैं।

पैरामीट्रिक बहुरूपता

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

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

लेकिन Maybeप्रकार के बारे में क्या ? यदि आप इससे परिचित नहीं हैं, Maybeतो एक प्रकार है जिसका शायद मूल्य है और शायद नहीं। आप इसका उपयोग कहां करेंगे? ठीक है, उदाहरण के लिए, जब किसी आइटम को एक शब्दकोश से बाहर निकाला जा रहा है: तथ्य यह है कि एक आइटम शब्दकोश में नहीं हो सकता है एक असाधारण स्थिति नहीं है, इसलिए यदि आइटम नहीं है, तो आपको वास्तव में एक अपवाद नहीं फेंकना चाहिए। इसके बजाय, आप एक उपप्रकार की एक आवृत्ति लौटाते हैं Maybe<T>, जिसमें ठीक दो उपप्रकार होते हैं: Noneऔर Some<T>int.Parseकिसी चीज़ का एक और उम्मीदवार जो वास्तव में Maybe<int>एक अपवाद या पूरे int.TryParse(out bla)नृत्य को फेंकने के बजाय वापस लौटना चाहिए ।

अब, आप तर्क कर सकते हैं कि Maybeएक सूची की तरह थोड़े-थोड़े हैं जो केवल शून्य या एक तत्व हो सकते हैं। और इस प्रकार थोरा-संग्रह एक संग्रह।

फिर किस बारे में Task<T>? यह एक प्रकार है जो भविष्य में किसी बिंदु पर एक मूल्य वापस करने का वादा करता है, लेकिन जरूरी नहीं कि अभी एक मूल्य हो।

या किस बारे में Func<T, …>? यदि आप प्रकारों पर अमूर्त नहीं कर सकते हैं, तो आप एक प्रकार से दूसरे प्रकार के फ़ंक्शन की अवधारणा का प्रतिनिधित्व कैसे करेंगे?

या, आम तौर पर: यह मानते हुए कि अमूर्त और पुन: उपयोग सॉफ्टवेयर इंजीनियरिंग के दो मौलिक संचालन हैं, आप प्रकारों पर अमूर्त करने में सक्षम क्यों नहीं होना चाहेंगे?

बहुरूपता को बांधा

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

आइए संग्रह पर वापस जाएं। एक हैशटेबल लें। हमने ऊपर कहा है कि सूची में इसके तत्वों के बारे में कुछ भी जानने की आवश्यकता नहीं है। खैर, एक हैशटेबल करता है: यह जानना आवश्यक है कि यह उन्हें हैश कर सकता है। (नोट: C # में, सभी ऑब्जेक्ट्स हैज़ेबल हैं, जैसे सभी ऑब्जेक्ट्स की समानता के लिए तुलना की जा सकती है। यह सभी भाषाओं के लिए सही नहीं है, हालांकि, और कभी-कभी C # में भी डिज़ाइन की गलती मानी जाती है।)

इसलिए, आप अपने प्रकार के पैरामीटर को हैशटेबल में प्रमुख प्रकार के लिए कसना चाहते हैं IHashable:

class HashTable<K, V> where K : IHashable
{
  Maybe<V> Get(K key);
  bool Add(K key, V value);
}

कल्पना करें कि इसके बजाय आपके पास यह था:

class HashTable
{
    object Get(IHashable key);
    bool Add(IHashable key, object value);
}

valueवहाँ से बाहर निकलने के साथ आप क्या करेंगे ? आप इसके साथ कुछ नहीं कर सकते, आप केवल यह जानते हैं कि यह एक वस्तु है। और यदि आप इस पर पुनरावृत्ति करते हैं, तो आप सभी प्राप्त करते हैं IHashableजो आपको पता है कि एक जोड़ी है जो एक है (जो आपको बहुत मदद नहीं करता है क्योंकि इसमें केवल एक ही संपत्ति है Hash) और कुछ आप जानते हैं कि यह एक है object(जो आपको और भी कम मदद करता है)।

या आपके उदाहरण के आधार पर कुछ:

class Repository<T> where T : ISerializable
{
    T Get(int id);
    void Save(T obj);
    void Delete(T obj);
}

आइटम को क्रमबद्ध करने की आवश्यकता है क्योंकि यह डिस्क पर संग्रहीत होने जा रहा है। लेकिन अगर आपके पास इसके बजाय क्या है:

class Repository
{
    ISerializable Get(int id);
    void Save(ISerializable obj);
    void Delete(ISerializable obj);
}

सामान्य मामले के साथ, यदि आप एक डाल BankAccountमें, आप एक पाने के BankAccountलिए वापस, तरीके और गुण की तरह साथ Owner, AccountNumber, Balance, Deposit, Withdraw, आदि कुछ आप के साथ काम कर सकते हैं। अब, अन्य मामला? तुम एक में डाल दिया BankAccountलेकिन तुम वापस एक मिल Serializableजो सिर्फ एक संपत्ति है,: AsString। तुम क्या करने जा रहे हो?

कुछ साफ-सुथरी चालें भी हैं जो आप बंधे हुए बहुरूपता के साथ कर सकते हैं:

एफ-बंधित बहुरूपता

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

interface ICloneable
{
    public this Clone(); // syntax I invented for a MyType feature
}

बंधी हुई बहुरूपता वाली भाषा में, आप इसके बजाय कुछ ऐसा कर सकते हैं:

interface ICloneable<T> where T : ICloneable<T>
{
    public T Clone();
}

class Foo : ICloneable<Foo>
{
    public Foo Clone()
    {
        // …
    }
}

ध्यान दें कि यह MyType संस्करण जितना सुरक्षित नहीं है, क्योंकि टाइप करने वाले के लिए "गलत" वर्ग को पास करने से कोई रोक नहीं सकता है:

class EvilBar : ICloneable<SomethingTotallyUnrelatedToBar>
{
    public SomethingTotallyUnrelatedToBar Clone()
    {
        // …
    }
}

सार प्रकार सदस्य

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

मूल रूप से, स्काला में, जैसे आपके पास सदस्य के रूप में फ़ील्ड और गुण और विधियाँ हो सकती हैं, आपके पास भी प्रकार हो सकते हैं। और जैसे खेतों और संपत्तियों और तरीकों को बाद में एक उपवर्ग में लागू करने के लिए अमूर्त छोड़ा जा सकता है, प्रकार के सदस्यों को भी सार छोड़ दिया जा सकता है। आइए संग्रह पर वापस जाएं, एक सरल List, जो कुछ इस तरह दिखाई देगा, अगर यह C # में समर्थित हो:

class List
{
    T; // syntax I invented for an abstract type member
    T Get(int index) { /* … */ }
    void Add(T obj) { /* … */ }
}

class IntList : List
{
    T = int;
}
// this is equivalent to saying `List<int>` with generics

मैं समझता हूं, कि प्रकारों पर अमूर्तता उपयोगी है। मैं सिर्फ "वास्तविक जीवन" में इसका उपयोग नहीं देखता। फंक <> और टास्क <> और एक्शन <> लाइब्रेरी प्रकार हैं। और धन्यवाद मुझे interface IFoo<T> where T : IFoo<T>भी याद आया । यह स्पष्ट रूप से वास्तविक जीवन का अनुप्रयोग है। उदाहरण बहुत अच्छा है। लेकिन किसी कारण से मैं संतुष्ट महसूस नहीं कर रहा हूँ। मैं इसके बजाय अपने मन को प्राप्त करना चाहता हूं जब यह उचित है और जब यह नहीं है। यहाँ जवाबों का इस प्रक्रिया में कुछ योगदान है, लेकिन मैं अभी भी इस सब के आसपास खुद को अधूरा महसूस कर रहा हूं। यह अजीब है क्योंकि भाषा-स्तर की समस्याएं मुझे बहुत पहले से परेशान नहीं करती हैं।
जंगल_मोल

महान उदाहरण। मुझे लगा जैसे मैं क्लास रूम में वापस आ गया हूं। +1
Chef_Code

1
@Chef_Code: मैं आशा तारीफ :-P है कि
Jörg डब्ल्यू Mittag

हाँ यही है। बाद में मैंने इस बारे में सोचा कि मेरे द्वारा पहले ही टिप्पणी करने के बाद इसे कैसे माना जा सकता है। तो ईमानदारी की पुष्टि करने के लिए ... हाँ यह एक तारीफ है और कुछ नहीं।
शेफ_कोड

14

पूरी बात यह है: कुछ भी संग्रह के अलावा सामान्य के लिए अच्छा है और बाधाओं टाइप सामान्य के रूप में विशेष के रूप में सामान्य नहीं बनाते हैं, वर्ग के अंदर सामान्य प्रकार पैरामीटर के बजाय इस प्रकार की बाधा का उपयोग करता है?

नहीं, आप बहुत अधिक के बारे में सोच रहे हैं Repository, जहां यह है बहुत ज्यादा एक ही। लेकिन यह नहीं है कि जेनरिक के लिए क्या कर रहे हैं। वे वहाँ उपयोगकर्ताओं के लिए कर रहे हैं ।

यहाँ मुख्य बिंदु यह नहीं है कि रिपॉजिटरी स्वयं अधिक सामान्य है। यह है कि उपयोगकर्ता अधिक विशिष्ट हैं- यानी, Repository<BusinessObject1>और Repository<BusinessObject2>विभिन्न प्रकार हैं, और इसके अलावा, कि अगर मैं एक लेता Repository<BusinessObject1>हूं, तो मुझे पता है कि मैं BusinessObject1वापस बाहर निकलूंगा Get

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


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

@zloidooraque: इसके अलावा IntelliSense को यह नहीं पता होता है कि किस प्रकार के ऑब्जेक्ट को भंडार में संग्रहीत किया जाता है। लेकिन हाँ, आप जेनरिक के बिना कुछ भी कर सकते हैं यदि आप इसके बजाय जातियों का उपयोग करने के लिए तैयार हैं।
gexicide

@gexicide वह बिंदु है: मैं नहीं देख सकता कि मुझे कॉस्ट का उपयोग करने की आवश्यकता है अगर मैं सामान्य इंटरफ़ेस का उपयोग करता हूं। मैंने कभी भी "उपयोग Object" नहीं कहा है । मुझे यह भी समझ में आया है कि संग्रह (DRY सिद्धांत) लिखते समय जेनेरिक का उपयोग क्यों किया जाता है। संभवतः, मेरा प्रारंभिक प्रश्न संग्रह संदर्भ के बाहर जेनेरिक का उपयोग करने के बारे में कुछ होना चाहिए था
जंगल_मूल

@ ज़्लोयूरैक: इसका पर्यावरण से कोई लेना-देना नहीं है। Intellisense आपको बता नहीं सकता है कि क्या IBusinessObjecta BusinessObject1या a है BusinessObject2। यह व्युत्पन्न प्रकार के आधार पर ओवरलोड को हल नहीं कर सकता है जो यह नहीं जानता है। यह उस कोड को अस्वीकार नहीं कर सकता है जो गलत प्रकार से गुजरता है। मजबूत टाइपिंग के एक लाख बिट्स हैं जो कि इन्टेलिसेन्स के बारे में बिल्कुल कुछ नहीं कर सकते हैं। बेहतर टूलींग समर्थन एक अच्छा लाभ है, लेकिन वास्तव में मूल कारणों से कोई लेना देना नहीं है।
मृत सीएमजी

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

13

"इस रिपॉजिटरी के सबसे अधिक संभावना वाले ग्राहक IBusinessObject इंटरफ़ेस के माध्यम से वस्तुओं को प्राप्त करना और उनका उपयोग करना चाहेंगे"।

नहीं, वे नहीं करेंगे।

आइए विचार करें कि IBusinessObject की निम्नलिखित परिभाषा है:

public interface IBusinessObject
{
  public int Id { get; }
}

यह सिर्फ आईडी को परिभाषित करता है क्योंकि यह सभी व्यावसायिक वस्तुओं के बीच एकमात्र साझा कार्यक्षमता है। और आपके पास दो वास्तविक व्यावसायिक वस्तुएं हैं: व्यक्ति और पता (चूंकि व्यक्तियों के पास सड़कें और पते नहीं हैं और न ही ऐसे नाम हैं, जो आप दोनों को मज़ेदार इंटरफ़ेस के साथ दोनों को मज़बूत करने के लिए विवश नहीं कर सकते। यह एक भयानक डिजाइन होगा, जिसका उल्लंघन होगा। इंटरफ़ेस सहानुभूति सिद्धांत , "I" में SOLID )

public class Person : IBusinessObject
{
  public int Id { get; private set; }
  public string Name { get; private set; }
}

public class Address : IBusinessObject
{
  public int Id { get; private set; }
  public string City { get; private set; }
  public string StreetName { get; private set; }
  public int Number { get; private set; }
}

अब, जब आप रिपॉजिटरी के सामान्य संस्करण का उपयोग करते हैं तो क्या होता है:

public class Repository<T> where T : class, IBusinessObject
{
  T Get(int id)
  void Save(T obj);
  void Delete(T obj);
}

जब आप जेनेरिक रिपॉजिटरी पर गेट मेथड को कॉल करते हैं, तो लौटी हुई वस्तु को दृढ़ता से टाइप किया जाएगा, जिससे आप सभी वर्ग के सदस्यों को एक्सेस कर सकेंगे।

Person p = new Repository<Person>().Get(1);
int id = p.Id;
string name = p.Name;

Address a = new Repository<Address>().Get(1);
int id = a.Id;
string cityName = a.City;
int houseNumber = a.Number;

दूसरी ओर, जब आप गैर-जेनेरिक रिपॉजिटरी का उपयोग करते हैं:

public class Repository
{
  IBusinessOBject Get(int id)
  void Save(IBusinessOBject obj);
  void Delete(IBusinessOBject obj);
}

आप केवल IBusinessObject इंटरफ़ेस से सदस्यों तक पहुँचने में सक्षम होंगे:

IBussinesObject p = new Repository().Get(1);
int id = p.Id; //OK
string name = p.Name; //Oooops, you dont have "Name" defined on the IBussinesObject interface.

इसलिए, निम्न पंक्तियों के कारण पिछला कोड संकलित नहीं किया जाएगा:

string name = p.Name;
string cityName = a.City;
int houseNumber = a.Number;

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.