डाउनकास्टिंग से कैसे बचें?


12

मेरा सवाल सुपर क्लास एनिमल के एक विशेष मामले के बारे में है।

  1. मेरा Animalकर सकते हैं moveForward()और eat()
  2. Sealफैली हुई है Animal
  3. Dogफैली हुई है Animal
  4. और एक विशेष प्राणी है जिसे Animalकहा जाता है Human
  5. Humanऔजार भी एक विधि है speak()(द्वारा कार्यान्वित नहीं Animal)।

एक अमूर्त विधि के कार्यान्वयन में जो स्वीकार करता है कि Animalमैं इस speak()पद्धति का उपयोग करना चाहूंगा । ऐसा लगता है कि डाउनकास्ट किए बिना संभव नहीं है। जेरेमी मिलर ने अपने लेख में लिखा है कि एक डाउनकास्ट में बदबू आती है।

इस स्थिति में डाउनकास्टिंग से बचने के लिए क्या उपाय होगा?


6
अपने मॉडल को ठीक करें। एक वर्ग पदानुक्रम को मॉडल करने के लिए मौजूदा टैक्सोनॉमिक पदानुक्रम का उपयोग करना आमतौर पर गलत विचार है। आपको अपने सार को मौजूदा कोड से खींचना चाहिए, सार को नहीं बनाना चाहिए और फिर उस पर कोड फिट करने का प्रयास करना चाहिए।
व्यंग्यात्मक

2
यदि आप चाहते हैं कि जानवर बोलें, तो बोलने की क्षमता एक जानवर का क्या हिस्सा है: artima.com/interfacedesign/PreferPoly.html
JeffO

8
आगे बढ़ो? केकड़ों के बारे में क्या?
फैबियो मारकोलिनी

कौन सा आइटम # प्रभावी जावा, BTW में है?
गोल्डीलॉक्स

1
कुछ लोग बोल नहीं सकते, इसलिए यदि आप कोशिश करते हैं तो एक अपवाद फेंक दिया जाता है।
ट्यूलेंस कोर्डोवा

जवाबों:


12

यदि आपके पास एक ऐसी विधि है जो यह जानने की आवश्यकता है कि कुछ करने के लिए विशिष्ट वर्ग किस प्रकार Humanका है, तो आप कुछ ठोस विशेष रूप से तोड़ रहे हैं :

  • खुला / बंद सिद्धांत - यदि, भविष्य में, आपको एक नया पशु प्रकार जोड़ना होगा जो बोल सकता है (उदाहरण के लिए, एक तोता), या उस प्रकार के लिए कुछ विशिष्ट कर सकता है, तो आपके मौजूदा कोड को बदलना होगा
  • इंटरफ़ेस पृथक्करण सिद्धांत - ऐसा लगता है कि आप बहुत अधिक सामान्य कर रहे हैं। एक जानवर प्रजातियों के व्यापक स्पेक्ट्रम को कवर कर सकता है।

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

कुछ इस तरह :

public void MakeItSpeak( Human obj );

और यह पसंद नहीं है:

public void SpeakIfHuman( Animal obj );

एक भी Animalबुलाया पर एक अमूर्त विधि बना सकता है canSpeakऔर प्रत्येक ठोस कार्यान्वयन को परिभाषित करना चाहिए कि क्या यह "बोल" सकता है या नहीं।
ब्रैंडन

लेकिन मुझे आपका जवाब बेहतर लगा। बहुत जटिलता कुछ ऐसी चीज के इलाज की कोशिश से आती है जो यह नहीं है।
ब्रैंडन

@ मुझे लगता है कि उस मामले में, अगर यह "बोल" नहीं सकता है, तो एक अपवाद फेंक दें। या सिर्फ कुछ नहीं करते।
B20овиЈ

तो आप इस तरह से ओवरलोडिंग करते हैं:public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}public void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
बार्ट वेबर

1
सभी तरह से जाएं - makeItSpeak (ISpeakingAnimal animal) - तब आप ISpeakingAnimal जानवरों को लगा सकते हैं। आप मेक इटस्पीक (पशु जानवर) {बोल सकते हैं अगर इंस्टाफॉन्ग ISpeakingAnimal}, लेकिन यह एक बेहोश गंध है।
20

6

समस्या यह नहीं है कि आप डाउनकास्टिंग कर रहे हैं - यह है कि आप डाउनकास्टिंग कर रहे हैं Human। इसके बजाय, एक इंटरफ़ेस बनाएँ:

public interface CanSpeak{
    void speak();
}

public abstract class Animal{
    //....
}

public class Human extends Animal implements CanSpeak{
    public void speak(){
        //....
    }
}

public void mysteriousMethod(Animal animal){
    //....
    if(animal instanceof CanSpeak){
        ((CanSpeak)animal).speak();
    }else{
        //Throw exception or something
    }
    //....
}

इस तरह, शर्त यह नहीं है कि जानवर है Human- शर्त यह है कि वह बोल सकता है। इसका मतलब यह है कि जब तक वे लागू होते हैं mysteriousMethod, तब तक गैर-मानव उपवर्गों के साथ काम कर सकते हैं ।AnimalCanSpeak


किस मामले में इसे तर्क के रूप में पशु के बजाय कैनस्पेक के उदाहरण के रूप में लेना चाहिए
न्यूटोपियन

1
@Newtopian यह मानते हुए कि उस विधि से किसी भी चीज़ की आवश्यकता नहीं है Animal, और उस पद्धति के सभी उपयोगकर्ता उस वस्तु को धारण करेंगे, जिसे वे एक CanSpeakप्रकार के संदर्भ (या यहां तक ​​कि Humanसंदर्भ) के माध्यम से भेजना चाहते हैं । यदि ऐसा होता, तो वह तरीका Humanपहली बार में इस्तेमाल किया जा सकता था और हमें परिचय की आवश्यकता नहीं थी CanSpeak
इदं आर्य

इंटरफ़ेस से छुटकारा पाना विधि हस्ताक्षर में विशिष्ट होने से जुड़ा नहीं है आप इंटरफ़ेस से छुटकारा पा सकते हैं यदि और केवल अगर वहाँ केवल मनुष्य है और कभी भी केवल मनुष्य ही होंगे जो "कैनस्पीक" होगा।
न्यूटॉपियन

1
@Newtopian जिस कारण से हमने CanSpeakपहली बार पेश किया, वह यह नहीं है कि हमारे पास ऐसा कुछ है जो इसे लागू करता है ( Human) लेकिन हमारे पास ऐसा कुछ है जो इसका उपयोग करता है (विधि)। उस बिंदु CanSpeakको ठोस वर्ग से अलग करना है Human। यदि हमारे पास ऐसी विधियाँ नहीं हैं जो उन चीजों का इलाज करती हैं जो CanSpeakअलग-अलग हैं, तो उन चीजों को अलग करने का कोई मतलब नहीं होगा CanSpeak। हम एक इंटरफ़ेस सिर्फ इसलिए नहीं बनाते हैं क्योंकि हमारे पास एक विधि है ...
इदं आर्य

2

आप पशु में संचार कर सकते हैं। कुत्ते की छाल, मानव बोलता है, सील .. उह .. मुझे नहीं पता कि मुहर क्या करती है।

लेकिन ऐसा लगता है कि आपकी विधि को डिज़ाइन किया गया है अगर (पशु मानव है) बोलो ();

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


15
सील ओउ ओउ ओउ , लेकिन लोमड़ी अपरिभाषित है।

2

इस स्थिति speak()में, AbstractAnimalकक्षा में डिफ़ॉल्ट कार्यान्वयन होगा:

void speak() throws CantSpeakException {
  throw new CantSpeakException();
}

उस समय, आपको सार वर्ग में एक डिफ़ॉल्ट कार्यान्वयन मिला है - और यह सही ढंग से व्यवहार करता है।

try {
  thingy.speak();
} catch (CantSeakException e) {
  System.out.println("You can't talk to the " + thingy.name());
}

हां, इसका मतलब है कि आपने प्रत्येक को संभालने के लिए कोड के माध्यम से बिखरे हुए कैच ट्राई किए हैं speak, लेकिन इसका विकल्प if(thingy is Human)इसके बजाय सभी बोलियों को लपेटना है।

अपवाद का लाभ यह है कि यदि आपके पास किसी अन्य प्रकार की बात है जो बोलती है (तोता) तो आपको अपने सभी परीक्षणों को फिर से लागू करने की आवश्यकता नहीं होगी।


1
मुझे नहीं लगता कि यह अपवाद का उपयोग करने का एक अच्छा कारण है (जब तक कि यह अजगर कोड नहीं है)।
ब्रायन चेन

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

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

2
इस मामले में एक अपवाद फेंकने के लिए वैकल्पिक कुछ भी नहीं हो सकता है। आखिरकार, वे बोल नहीं सकते :)
BЈовић

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

1

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

जिस समय डाउनकास्टिंग को सबसे संदिग्ध माना जाना चाहिए, जब डाली जा रही वस्तु को उचित प्रकार का "ज्ञात" किया जाता है। सामान्य तौर पर, यदि किसी वस्तु को एक के रूप में जाना जाता है , तो उसे संदर्भित करने के लिए, किसी प्रकार के चर के बजाय, एक Catप्रकार के चर का उपयोग करना चाहिए । ऐसे समय होते हैं जब यह हमेशा काम नहीं करता है। उदाहरण के लिए, एक संग्रह वस्तुओं के जोड़े को सम / विषम सरणी स्लॉट में पकड़ सकता है, इस अपेक्षा के साथ कि प्रत्येक जोड़ी में ऑब्जेक्ट एक दूसरे पर कार्य करने में सक्षम होंगे, भले ही वे अन्य जोड़े में वस्तुओं पर कार्य न कर सकें। ऐसे मामले में, प्रत्येक जोड़ी में वस्तुओं को अभी भी एक गैर-विशिष्ट पैरामीटर प्रकार को स्वीकार करना होगा, जैसे कि वे, वाक्यात्मक रूप से , किसी अन्य जोड़ी से वस्तुओं को पारित कर सकते हैं। इस प्रकार, भले ही केCatAnimalZooCatplayWith(Animal other)विधि केवल तब काम करेगी जब otherएक था Cat, Zooउसे एक तत्व को पारित करने में सक्षम होने की आवश्यकता होगी Animal[], इसलिए इसके पैरामीटर प्रकार के Animalबजाय होना चाहिए Cat

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


स्ट्रिंग मामले में, आपके पास एक विधि होनी चाहिए Object.equalToString(String string)। फिर आपके पास boolean String.equal(Object object) { return object.equalStoString(this); }So, कोई डाउनकास्ट आवश्यक नहीं है: आप डायनेमिक डिस्पैचिंग का उपयोग कर सकते हैं।
जियोर्जियो

@ जियोर्जियो: डायनेमिक डिस्पैचिंग के इसके उपयोग हैं, लेकिन यह आमतौर पर डाउनकास्टिंग से भी बदतर है।
सुपरकैट

जिस तरह से गतिशील डिस्पैचिंग आमतौर पर डाउनकास्टिंग से भी बदतर है? हालांकि, यह अन्य तरीके से है
ब्रायन चेन

@ ब्रायनचेन: यह आपकी शब्दावली पर निर्भर करता है। मुझे नहीं लगता Objectकि किसी भी equalStoStringआभासी विधि है, और मैं मानता हूँ कि मैं नहीं जानता कि कैसे उद्धृत उदाहरण जावा में भी काम करेगा, लेकिन सी # में, गतिशील प्रेषण (आभासी प्रेषण से अलग) का मतलब होगा कि अनिवार्य रूप से संकलक है परावर्तन-आधारित नाम को देखने के लिए पहली बार एक विधि का उपयोग एक वर्ग पर किया जाता है, जो आभासी प्रेषण से अलग होता है (जो कि वर्चुअल विधि तालिका में एक स्लॉट के माध्यम से एक कॉल करता है जिसमें एक वैध विधि पता होना आवश्यक है)।
सुपरैट

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

1

अमूर्त पद्धति के कार्यान्वयन में जो पशु स्वीकार करता है मैं बोल () विधि का उपयोग करना चाहूंगा।

आपके पास कुछ विकल्प हैं:

  • speakमौजूद होने पर कॉल करने के लिए प्रतिबिंब का उपयोग करें । लाभ: पर निर्भरता नहीं Human। नुकसान: "बोल" नाम पर अब एक छिपी निर्भरता है।

  • एक नया इंटरफ़ेस पेश करें Speakerऔर इंटरफ़ेस को डाउनकास्ट करें। यह एक विशिष्ट कंक्रीट प्रकार के आधार पर अधिक लचीला है। इसका नुकसान यह है कि आपको Humanलागू करने के लिए संशोधित करना होगा Speaker। यदि आप संशोधित नहीं कर सकते हैं तो यह काम नहीं करेगाHuman

  • के लिए डाउनकास्ट Human। इसका नुकसान यह है कि जब भी आप बोलने के लिए एक और उपवर्ग चाहते हैं तो आपको कोड को संशोधित करना होगा। आदर्श रूप से आप बार-बार जाने और पुराने कोड को बदले बिना कोड जोड़कर अनुप्रयोगों का विस्तार करना चाहते हैं।

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