सार्वजनिक सदस्यों को कभी भी आभासी / अमूर्त न बनाएं - क्या वास्तव में?


20

2000 के दशक में वापस मेरे एक सहकर्मी ने मुझे बताया कि यह सार्वजनिक तरीकों को आभासी या अमूर्त बनाने का एक विरोधी पैटर्न है।

उदाहरण के लिए, उन्होंने एक वर्ग को इस तरह से डिजाइन करने पर विचार किया:

public abstract class PublicAbstractOrVirtual
{
  public abstract void Method1(string argument);

  public virtual void Method2(string argument)
  {
    if (argument == null) throw new ArgumentNullException(nameof(argument));
    // default implementation
  }
}

उन्होंने कहा कि

  • एक व्युत्पन्न वर्ग के डेवलपर जो कि लागू करता है Method1और ओवरराइड करता Method2है उसे तर्क सत्यापन को दोहराना पड़ता है।
  • मामले में आधार वर्ग के विकासकर्ता के अनुकूलन हिस्सा चारों ओर कुछ जोड़ने का निर्णय करता Method1या Method2बाद में, वह यह नहीं कर सकते।

इसके बजाय मेरे सहयोगी ने इस दृष्टिकोण का प्रस्ताव दिया:

public abstract class ProtectedAbstractOrVirtual
{
  public void Method1(string argument)
  {
    if (argument == null) throw new ArgumentNullException(nameof(argument));
    this.Method1Core(argument);
  }

  public void Method2(string argument)
  {
    if (argument == null) throw new ArgumentNullException(nameof(argument));
    this.Method2Core(argument);
  }

  protected abstract void Method1Core(string argument);

  protected virtual void Method2Core(string argument)
  {
    // default implementation
  }
}

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

लेकिन मैं इसे डिज़ाइन गाइडलाइन के रूप में नहीं देखता। यहां तक ​​कि Microsoft भी इसका पालन नहीं करता है: इसे Streamसत्यापित करने के लिए कक्षा पर एक नज़र डालें ।

उस गाइडलाइन से आप क्या समझते हैं? क्या इसका कोई मतलब है, या क्या आपको लगता है कि यह एपीआई को ओवरकंप्लीकेट कर रहा है?


5
बनाने की विधि virtual वैकल्पिक ओवरराइडिंग की अनुमति देती है। आपकी विधि संभवतः सार्वजनिक होनी चाहिए, क्योंकि यह ओवरराइड नहीं हो सकती है। विधियाँ बनाना abstract आपको उन्हें ओवरराइड करने के लिए मजबूर करता है; वे शायद होना चाहिए protected, क्योंकि वे एक publicसंदर्भ में विशेष रूप से उपयोगी नहीं हैं ।
रॉबर्ट हार्वे

4
वास्तव में, protectedसबसे अधिक उपयोगी है जब आप सार वर्ग के निजी सदस्यों को व्युत्पन्न कक्षाओं में उजागर करना चाहते हैं। किसी भी मामले में, मैं विशेष रूप से आपके मित्र की राय के बारे में चिंतित नहीं हूं; पहुँच संशोधक चुनें जो आपकी विशेष स्थिति के लिए सबसे अधिक समझ में आता है।
रॉबर्ट हार्वे

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

8
@GregBurghardt: ऐसा लगता है कि ओपी के सहकर्मी हमेशा टेम्पलेट विधि पैटर्न का उपयोग करने का सुझाव देते हैं , भले ही इसकी आवश्यकता हो या न हो। यह पैटर्न का एक विशिष्ट अति प्रयोग है - अगर किसी के पास एक हथौड़ा है, तो जल्द ही या बाद में हर समस्या एक कील की तरह दिखाई देने लगती है;;
Doc Brown

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

जवाबों:


30

कह रही है

यह एक व्युत्पन्न वर्ग के डेवलपर की वजह से सार्वजनिक तरीकों को आभासी या अमूर्त बनाने का एक विरोधी पैटर्न है जो Method1 को लागू करता है और Method2 को तर्क सत्यापन को दोहराता है।

कारण और प्रभाव को मिला रहा है। यह यह धारणा बनाता है कि प्रत्येक अधिगम योग्य विधि को गैर-अनुकूलन योग्य तर्क सत्यापन की आवश्यकता होती है। लेकिन यह काफी अन्य तरह से गोल है:

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

जैसे मानक तरीकों पर नज़र: लेकिन वहाँ जहां यह सही समझ में आता है एक सार्वजनिक आभासी विधि के लिए उदाहरण के बहुत सारे, के बाद से वहाँ कोई तय हो गई है कस्टमाइज़ न करने हिस्सा हैं ToStringया Equalsया GetHashCode- यह की डिजाइन में सुधार होगा objectवर्ग इन सार्वजनिक नहीं करने के लिए और एक ही समय में आभासी? मुझे ऐसा नहीं लगता।

या, अपने स्वयं के कोड के संदर्भ में: जब आधार वर्ग में कोड अंत में और जानबूझकर इस तरह दिखता है

 public void Method1(string argument)
 {
    // nothing to validate here, all strings including null allowed
    this.Method1Core(argument);
 }

के बीच यह अलगाव होने Method1और Method1Coreकेवल स्पष्ट कारण के लिए चीजों को जटिल करता है।


1
ToString()विधि के मामले में , Microsoft ने इसे गैर-आभासी बना दिया था और एक आभासी टेम्पलेट विधि पेश की थी ToStringCore()। क्यों: इस वजह से: ToString()- उत्तराधिकारियों को ध्यान दें । वे कहते हैं कि ToString()अशक्त नहीं लौटना चाहिए। वे इस मांग को लागू करके जोर दे सकते थे ToString() => ToStringCore() ?? string.Empty
पीटर पेरोट

9
@PeterPerot: वह दिशानिर्देश, जिसे आपने लिंक string.Emptyकिया था, उसे वापस न करने की अनुशंसा करता है , क्या आपने नोटिस किया था? और यह बहुत सी अन्य चीजों की सिफारिश करता है जो एक ToStringCoreविधि की तरह कुछ पेश करके कोड में लागू नहीं किया जा सकता है । तो यह तकनीक शायद इसके लिए सही उपकरण नहीं है ToString
डॉक्टर ब्राउन

3
@ थरोट: निश्चित रूप से कोई भी कारण या तर्क पा सकता है कि ToString और Equals या GetHashcode को अलग-अलग तरीके से डिज़ाइन किया जा सकता था, लेकिन आज वे जैसे हैं (कम से कम मुझे लगता है कि उनका डिज़ाइन एक अच्छा उदाहरण बनाने के लिए काफी अच्छा है)।
डॉक्टर ब्राउन

1
@PeterPerot "के anyObjectIGot.ToString()बजाय मैंने बहुत सारे कोड देखे anyObjectIGot?.ToString()" - यह कैसे प्रासंगिक है? आपका ToStringCore()दृष्टिकोण एक अशक्त स्ट्रिंग को वापस आने से रोकेगा, लेकिन यह तब भी फेंक देगा NullReferenceExceptionजब वस्तु अशक्त होगी।
आईएमपी

1
@PeterPerot मैं प्राधिकरण से तर्क देने की कोशिश नहीं कर रहा था। यह इतना नहीं है कि Microsoft उपयोग करता है public virtual, लेकिन यह कि ऐसे मामले हैं जिनमें public virtualठीक है। हम कोड को भविष्य का प्रमाण बनाने के लिए एक गैर-अनुकूलन योग्य भाग के लिए तर्क कर सकते हैं ... लेकिन यह काम नहीं करता है। वापस जा रहा है और इसे बदलकर व्युत्पन्न प्रकारों को तोड़ सकता है। इस प्रकार, यह कुछ भी नहीं कमाता है।
थरोट

6

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

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

इसे लागू करने के लिए नामकरण सम्मेलन (जिसे मैं जानता हूं) सार्वजनिक इंटरफ़ेस के लिए अच्छे नाम को आरक्षित करना और "डू" के साथ आंतरिक विधि के नाम का उपसर्ग करना है।

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


1
क्या विधि उपसर्ग एक विकल्प है। Microsoft अक्सर कोर मेथड उपसर्ग का उपयोग करता है ।
पीटर पेरोट

@ पैटर पेरोट। मैंने किसी भी Microsoft सामग्री में कोर उपसर्ग कभी नहीं देखा, लेकिन ऐसा इसलिए हो सकता है क्योंकि मैं हाल ही में ज्यादा ध्यान नहीं दे रहा हूं। मुझे संदेह है कि उन्होंने अभी हाल ही में कोर मोनीकर को बढ़ावा देने के लिए ऐसा करना शुरू किया था।
मार्टिन माट

नहींं, यह एक पुरानी टोपी है BindingList:। इसके अलावा मुझे कहीं न कहीं सिफारिश मिली, शायद उनके फ्रेमवर्क डिजाइन दिशानिर्देश या कुछ इसी तरह के। और: यह एक उपसर्ग है । ;-)
पीटर पेरोट

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

2

C ++ में, इसे गैर-वर्चुअल इंटरफ़ेस पैटर्न (NVI) कहा जाता है । (एक बार, इसे टेम्प्लेट मेथड कहा जाता था। यह भ्रामक था, लेकिन कुछ पुराने लेखों में यह शब्दावली है।) एनवीआई को हर्ब सटर द्वारा पदोन्नत किया गया है, जिन्होंने कम से कम कई बार इसके बारे में लिखा है। मुझे लगता है कि जल्द से जल्द एक यहाँ है

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

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

सरल उदाहरणों में, यह अक्सर एक सार्वजनिक चाल के लिए उबलता है जो सिर्फ एक निजी वर्चुअल रियेलडाउमनी को सभी काम सौंपता है, इसलिए यह बिना किसी लाभ के बहुत सारे ओवरहेड जैसा लगता है।

लेकिन यह एक-से-एक पत्राचार की आवश्यकता नहीं है। उदाहरण के लिए, आप आकृति के सार्वजनिक एपीआई में एक चेतन विधि जोड़ सकते हैं, और यह कार्यान्वित कर सकता है कि वास्तव में लूप में कॉल करें। आप दो सार्वजनिक गैर-वर्चुअल विधियों API के साथ समाप्त होते हैं जो दोनों एक निजी अमूर्त पद्धति पर निर्भर करते हैं। आपके मंडलियों और वर्गों को कोई अतिरिक्त काम करने की आवश्यकता नहीं है, न ही चेतन को ओवरराइड कर सकते हैं

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

मुझे C # और C ++ के बीच किसी भी अंतर के बारे में पता नहीं है जो वर्ग डिजाइन के इस पहलू को बदल देगा।


अच्छा खोजो! अब मुझे याद है कि मैंने 2000 के दशक में अपने 2 लिंक पॉइंट्स (या इसकी एक कॉपी) पोस्ट किया था। मुझे याद है कि मैं अपने सहकर्मी के दावे के अधिक सबूतों की खोज कर रहा था, और मुझे C # संदर्भ में कुछ भी नहीं मिला, लेकिन C ++। इस। है। यह! :-) लेकिन, C # भूमि में वापस, ऐसा लगता है कि इस पैटर्न का अधिक उपयोग नहीं किया गया है। हो सकता है कि लोगों को एहसास हुआ कि बाद में आधार कार्यक्षमता जोड़ने से व्युत्पन्न वर्ग भी टूट सकते हैं, और यह कि सार्वजनिक आभासी तरीकों के बजाय टीएमपी या एनवीआईपी का सख्त उपयोग हमेशा समझ में नहीं आता है।
पीटर पेरोट

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