ओवरराइडिंग ठोस तरीकों एक कोड गंध है?


31

क्या यह सच है कि ठोस तरीकों को ओवरराइड करना एक कोड गंध है? क्योंकि मुझे लगता है कि अगर आपको ठोस तरीकों से आगे निकलने की जरूरत है:

public class A{
    public void a(){
    }
}

public class B extends A{
    @Override
    public void a(){
    }
}

इसे फिर से लिखा जा सकता है

public interface A{
    public void a();
}

public class ConcreteA implements A{
    public void a();
}

public class B implements A{
    public void a(){
    }
}

और यदि B A में पुन: उपयोग करना चाहता है (तो) इसे फिर से लिखा जा सकता है:

public class B implements A{
    public ConcreteA concreteA;
    public void a(){
        concreteA.a();
    }
}

जो विधि को ओवरराइड करने के लिए विरासत की आवश्यकता नहीं है, क्या यह सच है?



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

5
प्रश्न जावा को टैग नहीं किया गया है, लेकिन कोड जावा प्रतीत होता है। C # (या C ++) में, आपको virtualओवरराइड का समर्थन करने के लिए कीवर्ड का उपयोग करना होगा । इसलिए, इस मुद्दे को भाषा के विवरण से अधिक (या कम) अस्पष्ट बना दिया जाता है।
एरिक Eidt

1
ऐसा लगता है कि लगभग हर प्रोग्रामिंग भाषा की सुविधा अनिवार्य रूप से पर्याप्त वर्षों के बाद "कोड गंध" के रूप में वर्गीकृत की जाती है।
ब्रेंडन

2
मुझे ऐसा लगता finalहै कि इन जैसी स्थितियों के लिए संशोधक बिल्कुल मौजूद है। यदि विधि का व्यवहार ऐसा है कि इसे ओवरराइड करने के लिए डिज़ाइन नहीं किया गया है, तो इसे चिह्नित करें final। अन्यथा, उम्मीद करें कि डेवलपर्स इसे ओवरराइड करने का विकल्प चुन सकते हैं।
मार्टिन टस्केविसियस

जवाबों:


34

नहीं, यह एक कोड गंध नहीं है।

  • यदि एक वर्ग अंतिम नहीं है, तो यह उपवर्ग होने की अनुमति देता है।
  • यदि कोई विधि अंतिम नहीं है, तो यह ओवरराइड होने की अनुमति देता है।

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

वर्ग खुद को या किसी भी विधि को अंतिम रूप से परिभाषित कर सकता है, या कैसे और कहाँ उप-वर्गित है, इस पर प्रतिबंध (दृश्यता संशोधक, उपलब्ध निर्माणकर्ता) लगा सकता है।

ओवरराइडिंग विधियों के लिए सामान्य मामला बेस क्लास में एक डिफ़ॉल्ट कार्यान्वयन है जो उपवर्ग में अनुकूलित किया जा सकता है या अनुकूलित किया जा सकता है (विशेषकर जावा 8 में इंटरफेस में डिफ़ॉल्ट तरीकों के आगमन के साथ)।

class A {
    public String getDescription(Element e) {
        // return default description for element
    }
}

class B extends A {
    public String getDescription(Element e) {
        // return customized description for element
    }
}

ओवरराइडिंग व्यवहार का एक विकल्प स्ट्रैटेजी पैटर्न है , जहां एक इंटरफेस के रूप में व्यवहार को अमूर्त किया जाता है, और कार्यान्वयन को कक्षा में सेट किया जा सकता है।

interface DescriptionProvider {
    String getDescription(Element e);
}

class A {
    private DescriptionProvider provider=new DefaultDescriptionProvider();

    public final String getDescription(Element e) {
       return provider.getDescription(e);
    }

    public final void setDescriptionProvider(@NotNull DescriptionProvider provider) {
        this.provider=provider;
    }
}

class B extends A {
    public B() {
        setDescriptionProvider(new CustomDescriptionProvider());
    }
}

मैं समझता हूं कि, वास्तविक दुनिया में, एक ठोस विधि को ओवरराइड करने से कुछ को कोड गंध हो सकता है। लेकिन, मुझे यह जवाब पसंद है क्योंकि यह मेरे लिए अधिक कल्पना लगता है।
डार्क वाटर 23

अपने अंतिम उदाहरण में, क्या Aलागू नहीं होना चाहिए DescriptionProvider?
स्पॉट किया गया

@Spotted: कार्यान्वित A कर सकता है DescriptionProvider, और इस प्रकार डिफ़ॉल्ट विवरण प्रदाता हो सकता है। लेकिन यहां विचार चिंताओं से अलग है: Aविवरण की आवश्यकता है (कुछ अन्य कक्षाएं भी हो सकती हैं), जबकि अन्य कार्यान्वयन उन्हें प्रदान करते हैं।
पीटर वालसर

2
ठीक है, रणनीति पैटर्न की तरह लगता है ।
स्पॉट किया गया

@ स्‍पष्‍ट: आप सही हैं, उदाहरण स्ट्रेटेजी पैटर्न को चित्रित करता है, न कि डेकोरेटर पैटर्न (एक डेकोरेटर एक घटक को व्यवहार जोड़ देगा)। मैं अपना उत्तर सही करूंगा, धन्यवाद।
पीटर वालसर

25

क्या यह सच है कि ठोस तरीकों को ओवरराइड करना एक कोड गंध है?

हां, सामान्य तौर पर, ठोस तरीकों को ओवरराइड करना एक कोड गंध है।

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

अब, ऐसे मामले हैं जहां यह किया जा सकता है और अच्छा है। अर्ध-तुच्छ विधियों को कुछ हद तक सुरक्षित रूप से सवार किया जा सकता है। आधार विधियाँ जो केवल एक "समझदार" प्रकार के व्यवहार के रूप में मौजूद हैं, जिसका अर्थ है ओवर-राइड होना कुछ अच्छे उपयोग हैं।

इसलिए, यह मुझे एक गंध की तरह लगता है - कभी-कभी अच्छा, आमतौर पर बुरा, सुनिश्चित करने के लिए एक नज़र डालें।


29
आपका स्पष्टीकरण ठीक है, फिर भी मैं बिल्कुल विपरीत निष्कर्ष पर आता हूं: "नहीं, सामान्य तरीकों से ओवरराइड करना एक कोड गंध नहीं है" - यह केवल एक कोड गंध है जब एक तरीके को ओवरराइड करता है जो ओवरराइड करने का इरादा नहीं था। ;-)
डॉक्टर ब्राउन

2
@DocBrown बिल्कुल! मैं यह भी देखता हूं कि स्पष्टीकरण (और विशेष रूप से निष्कर्ष) प्रारंभिक बयान का विरोध करता है।
Tersosauros

1
@Spotted: सरलतम प्रतिधारण एक Numberवर्ग है जिसमें एक increment()विधि है जो मान को उसके अगले मान्य मान पर सेट करती है। यदि आप इसे उप-वर्ग में रखते हैं, EvenNumberजहां increment()एक के बजाय दो जोड़ते हैं (और सेटर समरूपता लागू करता है), तो ओपी के पहले प्रस्ताव में कक्षा में हर पद्धति के डुप्लिकेट कार्यान्वयन की आवश्यकता होती है और उसके दूसरे सदस्य में विधियों को कॉल करने के लिए लिखने के तरीकों की आवश्यकता होती है। वंशानुक्रम के उद्देश्य का एक हिस्सा बच्चे की कक्षाओं के लिए यह स्पष्ट करना है कि वे केवल उन तरीकों को प्रदान करके माता-पिता से अलग कैसे हैं जिन्हें अलग होने की आवश्यकता है।
ब्लरफ्ल

5
@DocBrown: कोड गंध होने का मतलब यह नहीं है कि कुछ जरूरी है , केवल इतना बुरा है कि यह संभावना है
मिकोलाक

3
@docbrown - मेरे लिए, यह "वंशानुक्रम पर अनुकूल रचना" के साथ आता है। यदि आप ठोस तरीकों की सवारी कर रहे हैं, तो आप पहले से ही उत्तराधिकार के लिए छोटी-सी गंध वाली भूमि में हैं। ऐसी कौन-सी बातें हैं जो आप पर हावी हो रही हैं। मेरे अनुभव के आधार पर, मुझे लगता है कि यह संभावित है। YMMV।
तेलस्टिन

7

यदि आपको ठोस तरीकों को ओवरराइड करने की आवश्यकता है [...] तो इसे फिर से लिखा जा सकता है

यदि यह इस तरह से फिर से लिखा जा सकता है तो इसका मतलब है कि आपका दोनों वर्गों पर नियंत्रण है। लेकिन फिर आप जानते हैं कि क्या आपने क्लास ए को इस तरह से बनाया गया है और यदि आपने ऐसा किया है, तो यह कोड गंध नहीं है।

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


यदि कक्षा A को व्युत्पन्न करने के लिए डिज़ाइन किया गया होता, तो स्पष्ट रूप से अभिप्राय व्यक्त करने के लिए एक अमूर्त पद्धति का होना अधिक समझ में नहीं आता? जो विधि को ओवरराइड करता है वह कैसे जान सकता है कि विधि इस तरह से डिजाइन की गई थी?
चित्तीदार

@ देखा गया, ऐसे कई मामले हैं जिनमें डिफ़ॉल्ट कार्यान्वयन कैम प्रदान किए जाते हैं और प्रत्येक विशेषज्ञता को केवल उनमें से कुछ को ओवरराइड करने की आवश्यकता होती है, या तो कार्यक्षमता बढ़ाने के लिए या दक्षता के लिए। चरम उदाहरण आगंतुकों है। उपयोगकर्ता जानता है कि कैसे प्रलेखन से तरीके तैयार किए गए थे; यह बताने के लिए वहाँ होना चाहिए कि ओवरराइड की उम्मीद किस तरह से है।
जन हडेक

मुझे आपकी बात समझ में आ रही है, फिर भी मुझे लगता है कि यह खतरनाक है कि एक डेवलपर के पास एकमात्र तरीका है, जिसमें किसी विधि को जानने के लिए ओवरराइड किया जा सकता है, यह है कि डॉक को पढ़कर: 1) यदि यह ओवरराइड होना चाहिए, तो मुझे इसकी कोई गारंटी नहीं है हो जाएगा। 2) यदि यह अतिरंजित नहीं होना चाहिए, तो कोई इसकी सुविधा के लिए करेगा 3) हर कोई डॉक्टर को नहीं पढ़ता है। इसके अलावा, मुझे कभी भी ऐसे मामले का सामना नहीं करना पड़ा जहाँ मुझे पूरी विधि को ओवरराइड करने की आवश्यकता थी, लेकिन केवल कुछ हिस्सों (और मैंने एक टेम्पलेट पैटर्न का उपयोग करके समाप्त किया)।
देखा गया

@Spotted, 1) यह अगर चाहिए ओवरराइड किया जा, यह होना चाहिए abstract; लेकिन ऐसे कई मामले हैं जहां यह नहीं होना चाहिए। 2) यदि यह नहीं होना चाहिए ओवरराइड होना चाहिए, तो यह final(गैर-आभासी) होना चाहिए । लेकिन अभी भी कई मामले हैं जहां तरीकों को ओवरराइड किया जा सकता है। 3) यदि डाउनस्ट्रीम डेवलपर डॉक्स नहीं पढ़ते हैं, तो वे खुद को पैर में गोली मारने जा रहे हैं, लेकिन वे ऐसा करने जा रहे हैं, चाहे आप कोई भी उपाय करें।
Jan Hudec

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

3

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

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


मैं तुम्हें एक जावा पृष्ठभूमि, जहां तरीकों को स्पष्ट रूप से किया जाना चाहिए से आ रहे हैं अनुमान लगा रहा हूँ अंतिम के रूप में चिह्नित करता है, तो अनुबंध डिजाइनर उन्हें ओवरराइड नहीं करने का इरादा रखता है। हालाँकि, C # जैसी भाषाओं में, विपरीत डिफ़ॉल्ट है। विधियाँ (बिना किसी विशेष सजावट के, विशेष खोजशब्द) " सील " (C # 'संस्करण final), और स्पष्ट रूप से घोषित कीvirtual जानी चाहिए जैसे कि उन्हें ओवरराइड करने की अनुमति है।

अगर इन भाषाओं में एक एपीआई या लाइब्रेरी को एक ठोस विधि के साथ डिज़ाइन किया गया है , जो अति-योग्य है , तो इसे (कम से कम कुछ मामलों में) इसे ओवरराइड किया जाना चाहिए। इसलिए, एक अनसॉल्वड / वर्चुअल / नॉट finalकंक्रीट विधि को ओवरराइड करने के लिए यह कोड गंध नहीं है ।     (यह मानता है कि एपीआई के डिजाइनर परंपराओं का पालन कर रहे हैं और या तो virtual(C #) के रूप में चिह्नित कर रहे हैं , या final(जावा) के रूप में चिह्नित कर रहे हैं कि वे क्या डिजाइन कर रहे हैं।


यह भी देखें


बतख टाइपिंग में, दो वर्गों के पास समान प्रकार के हस्ताक्षर होते हैं जो उनके लिए संगत होते हैं, या क्या यह भी आवश्यक है कि विधियों में एक ही शब्दार्थ है, जैसे कि वे कुछ निहित आधार वर्ग के लिए प्रतिस्थापन हैं?
वेन कॉनराड

2

हां, यह इसलिए है क्योंकि यह सुपर एंटी-पैटर्न कॉल कर सकता है । यह विधि के प्रारंभिक उद्देश्य को भी निरूपित कर सकता है, साइड-इफेक्ट्स (=> बग) को लागू कर सकता है और परीक्षण विफल कर सकता है। क्या आप एक वर्ग का उपयोग करेंगे जो इकाई परीक्षण लाल (फैशिंग) हैं? मैं नहीं करूंगा

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

  • सार: आप एक व्यवहार प्रदान करना चाहिए
  • फाइनल: आप नहीं हैं व्यवहार को संशोधित करने की अनुमति है

मौजूदा कक्षा में व्यवहार को जोड़ने का सबसे सुरक्षित तरीका आपका अंतिम उदाहरण दिखाता है: डेकोरेटर पैटर्न का उपयोग करना।

public interface A{
    void a();
}

public final class ConcreteA implements A{
    public void a() {
        ...
    }
}

public final class B implements A{
    private final ConcreteA concreteA;
    public void a(){
        //Additionnal behaviour
        concreteA.a();
    }
}

9
यह श्वेत-श्याम दृष्टिकोण वास्तव में उपयोगी नहीं है। Object.toString()जावा के बारे में सोचें ... यदि यह abstractहोता, तो कई डिफ़ॉल्ट चीजें काम नहीं करतीं, और कोड भी संकलित नहीं होता जब किसी ने toString()विधि को ओवरराइड नहीं किया है ! यदि ऐसा finalहोता, तो चीजें और भी बदतर होतीं - जावा के अलावा, वस्तुओं को स्ट्रिंग में परिवर्तित करने की अनुमति देने की कोई क्षमता नहीं। के रूप में @Peter Walser के बारे में के जवाब वार्ता, डिफ़ॉल्ट कार्यान्वयन (जैसे Object.toString()) कर रहे हैं महत्वपूर्ण है। विशेष रूप से जावा 8 डिफ़ॉल्ट तरीकों में।
Tersosauros

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

शब्द "retrocompatibility" में बहुत सारे शब्दांश हैं।
क्रेग

@ इस साइट पर ठोस विरासत की सामान्य स्वीकृति पर मैं बहुत निराश हूं, मुझे बेहतर उम्मीद थी: / आपके उत्तर में कई और
बदलाव

1
@ TheWatWhisperer मैं सहमत हूं, लेकिन दूसरी ओर मुझे कंक्रीट वंशानुक्रम पर सजावट का उपयोग करने के उपशीर्षक लाभों का पता लगाने के लिए मुझे इतना समय लगा (वास्तव में यह स्पष्ट हो गया जब मैंने पहले परीक्षण लिखना शुरू किया था) कि आप किसी को दोष नहीं दे सकते । सबसे अच्छी बात यह है कि जब तक वे सत्य (मजाक) की खोज नहीं करते तब तक दूसरों को शिक्षित करने का प्रयास करें । :)
चित्तीदार
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.