जावा में Instof से बचना


102

"इंस्टोफ़" संचालन की एक श्रृंखला होने को "कोड गंध" माना जाता है। मानक उत्तर "बहुरूपता का उपयोग करें" है। मैं इस मामले में कैसे करूंगा?

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

if (obj instanceof Integer) {NumberStuff.handle((Integer)obj);}
else if (obj instanceof BigDecimal) {BigDecimalStuff.handle((BigDecimal)obj);}
else if (obj instanceof Double) {DoubleStuff.handle((Double)obj);}

मेरा नंबरस्टाफ और इतने पर नियंत्रण है।

मैं कोड की कई पंक्तियों का उपयोग नहीं करना चाहता जहाँ कुछ पंक्तियाँ होतीं। (कभी-कभी मैं एक HashMap मानचित्रण Integer.class को IntegerStuff, BigDecimal.class के उदाहरण से BigDecimalStuff आदि के उदाहरण के लिए बनाता हूं लेकिन आज मैं कुछ सरल करना चाहता हूं।)

मैं इस के रूप में कुछ सरल करना चाहते हैं:

public static handle(Integer num) { ... }
public static handle(BigDecimal num) { ... }

लेकिन जावा सिर्फ इस तरह से काम नहीं करता है।

प्रारूपण करते समय मैं स्थैतिक तरीकों का उपयोग करना चाहूंगा। मैं जिन चीजों का प्रारूपण कर रहा हूं, वे समग्र हैं, जहां एक Thing1 में एक सरणी Thing2s हो सकती है और एक Thing2 में Thing1s की एक सरणी हो सकती है। जब मैंने अपने फॉर्मेटर्स को इस तरह लागू किया तो मुझे एक समस्या हुई:

class Thing1Formatter {
  private static Thing2Formatter thing2Formatter = new Thing2Formatter();
  public format(Thing thing) {
      thing2Formatter.format(thing.innerThing2);
  }
}
class Thing2Formatter {
  private static Thing1Formatter thing1Formatter = new Thing1Formatter();
  public format(Thing2 thing) {
      thing1Formatter.format(thing.innerThing1);
  }
}

हाँ, मुझे पता है कि HashMap और थोड़ा अधिक कोड भी इसे ठीक कर सकता है। लेकिन "Instof" तुलना के हिसाब से इतना पठनीय और सुगम है। वहाँ कुछ भी सरल है, लेकिन बदबूदार नहीं है?

नोट जोड़ा गया 5/10/2010:

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

if (obj instanceof SubClass1) {
    // Handle all the methods and properties of SubClass1
} else if (obj instanceof SubClass2) {
    // Handle all the methods and properties of SubClass2
} else if (obj instanceof Interface3) {
    // Unknown class but it implements Interface3
    // so handle those methods and properties
} else if (obj instanceof Interface4) {
    // likewise.  May want to also handle case of
    // object that implements both interfaces.
} else {
    // New (unknown) subclass; do what I can with the base class
}

4
मैं एक [विज़िटर पैटर्न] [1] का सुझाव दूंगा। [१]: en.wikipedia.org/wiki/Visitor_pattern
lexicore

25
विज़िटर पैटर्न को लक्ष्य वर्ग (उदाहरण के लिए पूर्णांक) में एक विधि जोड़ने की आवश्यकता है - जावास्क्रिप्ट में आसान, जावा में कठिन। लक्ष्य कक्षाओं को डिजाइन करते समय उत्कृष्ट पैटर्न; इतना आसान नहीं है जब किसी पुराने क्लास के नए गुर सिखाने की कोशिश की जाए।
मार्क ल्यूटन

4
@lexicore: टिप्पणियों में मार्कडाउन सीमित है। [text](link)टिप्पणियों में लिंक पोस्ट करने के लिए उपयोग करें ।
बालुसक

2
"लेकिन जावा सिर्फ इस तरह से काम नहीं करता है।" शायद मैं चीजों को गलत समझ रहा हूं, लेकिन जावा विधि ओवरलोडिंग (यहां तक ​​कि स्थिर तरीकों पर भी) ठीक का समर्थन करता है ... यह सिर्फ इतना है कि ऊपर दिए गए आपके तरीके रिटर्न प्रकार को याद कर रहे हैं।
पॉवरलॉर्ड

4
@Powerlord अधिभार संकल्प संकलन-समय पर स्थिर है
Aleksandr Dubinsky

जवाबों:


55

आपको स्टीव येजे के अमेज़ॅन ब्लॉग से इस प्रविष्टि में रुचि हो सकती है: "जब बहुरूपता विफल हो जाती है" । अनिवार्य रूप से वह इस तरह के मामलों को संबोधित कर रहे हैं, जब बहुरूपता यह हल करने की तुलना में अधिक परेशानी का कारण बनता है।

मुद्दा यह है कि बहुरूपता का उपयोग करने के लिए आपको इस मामले में प्रत्येक 'स्विचिंग' क्लास - यानी इंटेगर आदि के "हैंडल" भाग का तर्क बनाना होगा। स्पष्ट रूप से यह व्यावहारिक नहीं है। कभी-कभी यह कोड डालने के लिए तार्किक रूप से सही स्थान भी नहीं होता है। वह कई बुराइयों के कम होने के रूप में 'इंस्टोफ' दृष्टिकोण की सिफारिश करता है।

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


22
बहुरूपता विफल नहीं होती है। इसके बजाय, स्टीव येजगे विजिटर पैटर्न का आविष्कार करने में विफल रहता है, जो कि इसके लिए सही प्रतिस्थापन है instanceof
रॉटसर

12
मैं यह नहीं देखता कि आगंतुक यहाँ कैसे मदद करता है। मुद्दा यह है कि NewMonster में OpinionatedElf की प्रतिक्रिया को NewMonster में एन्कोड नहीं किया जाना चाहिए, लेकिन OpinionatedElf में।
डीजेकेवर्थ

2
उदाहरण के बिंदु यह है कि OpinionatedElf उपलब्ध डेटा से यह नहीं बता सकता है कि वह राक्षस को पसंद या नापसंद करता है। यह जानना होगा कि द मॉन्स्टर किस क्लास का है। इसके लिए या तो एक Instof की आवश्यकता होती है, या मॉन्स्टर को किसी तरह से जानना होता है कि क्या OpinionatedElf को यह पसंद है। आगंतुक को वह दौर नहीं मिलता है।
डीजेकेवर्थ

2
@DJClayworth आगंतुक पैटर्न है कि करने के लिए एक विधि जोड़कर आसपास Monsterवर्ग, जिम्मेदारी जिनमें से, वस्तु पेश करने का मूल रूप से है, जैसे "हैलो, मैं एक Orc हूँ। आप मेरे बारे में क्या सोचते हैं?"। इसके बाद समान कोड वाले इन "अभिवादन" के आधार पर, विचारधारा वाले योगिनी राक्षसों का न्याय कर सकते हैं bool visitOrc(Orc orc) { return orc.stench()<threshold; } bool visitFlower(Flower flower) { return flower.colour==magenta; }। एकमात्र राक्षस-विशिष्ट कोड तब होगा class Orc { <T> T accept(MonsterVisitor<T> v) { v.visitOrc(this); } }, जो प्रत्येक राक्षस निरीक्षण के लिए एक बार और सभी के लिए पर्याप्त होगा ।
13

2
देखें @ क्रिस नाइट का कारण इस कारण से है कि विज़िटर को कुछ मामलों में लागू नहीं किया जा सकता है।
जेम्स पी।

20

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

public class IntegerWrapper {
    private Integer integer;
    public IntegerWrapper(Integer anInteger){
        integer = anInteger;
    }
    //Access the integer directly such as
    public Integer getInteger() { return integer; }
    //or method passthrough...
    public int intValue() { return integer.intValue(); }
    //then implement your visitor:
    public void accept(NumericVisitor visitor) {
        visitor.visit(this);
    }
}

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


हां, "फॉर्मैटर", "कम्पोजिट", "विभिन्न प्रकार" सभी सीमेन्ट आगंतुक की दिशा में इंगित करने के लिए।
थॉमस अहले

3
आप कैसे निर्धारित करते हैं कि आप किस रैपर का उपयोग करने जा रहे हैं? एक अगर उदाहरण शाखा के माध्यम से?
फास्ट टूथ

2
जैसा कि @fasttooth बताता है कि यह समाधान केवल समस्या को स्थानांतरित करता है। instanceofसही handle()तरीके से कॉल करने के लिए उपयोग करने के बजाय अब आपको इसे कॉल को सही XWrapperकंस्ट्रक्टर का उपयोग करना होगा ...
Matthias

15

एक विशाल के बजाय if, आप उन उदाहरणों को रख सकते हैं जिन्हें आप नक्शे में संभालते हैं (कुंजी: वर्ग, मूल्य: हैंडलर)।

यदि मुख्य रिटर्न द्वारा लुकअप null, एक विशेष हैंडलर विधि को कॉल करें जो एक मिलान हैंडलर खोजने की कोशिश करता है (उदाहरण के isInstance()लिए नक्शे में हर कुंजी पर कॉल करके )।

जब एक हैंडलर मिल जाए, तो उसे नई कुंजी के तहत पंजीकृत करें।

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


+1 मैंने इस दृष्टिकोण का उपयोग तब किया है जब XML स्कीमा या मैसेजिंग सिस्टम से उत्पन्न कोड को हैंडल किया जाता है, जहां दर्जनों प्रकार के ऑब्जेक्ट होते हैं, अनिवार्य रूप से गैर-टाइपफाइ तरीके से मेरे कोड को सौंप दिया जाता है।
डीएनए

13

आप प्रतिबिंब का उपयोग कर सकते हैं:

public final class Handler {
  public static void handle(Object o) {
    try {
      Method handler = Handler.class.getMethod("handle", o.getClass());
      handler.invoke(null, o);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  public static void handle(Integer num) { /* ... */ }
  public static void handle(BigDecimal num) { /* ... */ }
  // to handle new types, just add more handle methods...
}

आप उप-वर्ग और कक्षाओं को संभालने के लिए विचार पर विस्तार कर सकते हैं जो कुछ इंटरफेस को लागू करते हैं।


34
मैं यह तर्क दूंगा कि यह और भी अधिक बदबू आ रही है, तो संचालक। हालांकि काम करना चाहिए।
टिम बुथ

5
@ टिम ब्यूट: कम से कम आपको if then elseहैंडलर जोड़ने, हटाने या संशोधित करने के लिए एक बढ़ती श्रृंखला से निपटने की आवश्यकता नहीं है । कोड परिवर्तन के लिए कम नाजुक है। तो मैं कहूंगा कि इस कारण से यह instanceofदृष्टिकोण से बेहतर है । वैसे भी, मैं सिर्फ एक वैध विकल्प देना चाहता था।
जोर्डो

1
यह अनिवार्य रूप से कैसे एक गतिशील भाषा स्थिति को संभालती है, बतख टाइपिंग के
डीएनए

@DNA: कि नहीं होगा मुल्टीमेथड्स ?
जोर्डो

1
आप उपयोग करने के बजाय सभी तरीकों पर पुनरावृति क्यों करते हैं getMethod(String name, Class<?>... parameterTypes)? वरना मैं जगह लेंगे ==साथ isAssignableFromपैरामीटर के प्रकार की जांच के लिए।
Aleksandr Dubinsky

9

आप चेन ऑफ रिस्पॉन्सिबिलिटी पैटर्न पर विचार कर सकते हैं । अपने पहले उदाहरण के लिए, कुछ इस तरह:

public abstract class StuffHandler {
   private StuffHandler next;

   public final boolean handle(Object o) {
      boolean handled = doHandle(o);
      if (handled) { return true; }
      else if (next == null) { return false; }
      else { return next.handle(o); }
   }

   public void setNext(StuffHandler next) { this.next = next; }

   protected abstract boolean doHandle(Object o);
}

public class IntegerHandler extends StuffHandler {
   @Override
   protected boolean doHandle(Object o) {
      if (!o instanceof Integer) {
         return false;
      }
      NumberHandler.handle((Integer) o);
      return true;
   }
}

और फिर आपके अन्य हैंडलर के लिए भी। फिर यह क्रम में StuffHandlers को एक साथ स्ट्रिंग करने का मामला है (सबसे विशिष्ट से सबसे कम विशिष्ट, एक अंतिम 'फ़ॉलबैक' हैंडलर) के साथ, और आपका डिस्पैचर कोड बस है firstHandler.handle(o);

(एक विकल्प एक श्रृंखला का उपयोग करने के बजाय है, बस List<StuffHandler>आपके डिस्पैचर वर्ग में है, और जब तक handle()वह रिटर्न नहीं देता तब तक सूची में लूप है)।


9

मुझे लगता है कि सबसे अच्छा समाधान है क्लास के साथ हैशपेल को कुंजी के रूप में और हैंडलर को मूल्य के रूप में। ध्यान दें कि HashMap आधारित समाधान निरंतर एल्गोरिथम जटिलता in (1) में चलता है, जबकि अगर-इंस्टाफ़ॉफ़ की महक श्रृंखला रैखिक एल्गोरिथम कॉम्प्लेक्सिटी O (N) में चलती है, जहाँ N, इफ़-इंस्टोफ़-अन्य श्रृंखला में लिंक की संख्या है (यानी विभिन्न वर्गों की संख्या को संभाला जाएगा)। तो हैशपॅप आधारित सॉल्यूशन का प्रदर्शन, अगर-इंस्टोफ-अन्य चैन सॉल्यूशन के प्रदर्शन की तुलना में असमान रूप से अधिक N है। विचार करें कि आपको संदेश वर्ग के विभिन्न वंशों को अलग-अलग तरीके से संभालने की आवश्यकता है: संदेश 1, संदेश 2, आदि। नीचे HashMap आधारित हैंडलिंग के लिए कोड स्निपेट है।

public class YourClass {
    private class Handler {
        public void go(Message message) {
            // the default implementation just notifies that it doesn't handle the message
            System.out.println(
                "Possibly due to a typo, empty handler is set to handle message of type %s : %s",
                message.getClass().toString(), message.toString());
        }
    }
    private Map<Class<? extends Message>, Handler> messageHandling = 
        new HashMap<Class<? extends Message>, Handler>();

    // Constructor of your class is a place to initialize the message handling mechanism    
    public YourClass() {
        messageHandling.put(Message1.class, new Handler() { public void go(Message message) {
            //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message1
        } });
        messageHandling.put(Message2.class, new Handler() { public void go(Message message) {
            //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message2
        } });
        // etc. for Message3, etc.
    }

    // The method in which you receive a variable of base class Message, but you need to
    //   handle it in accordance to of what derived type that instance is
    public handleMessage(Message message) {
        Handler handler = messageHandling.get(message.getClass());
        if (handler == null) {
            System.out.println(
                "Don't know how to handle message of type %s : %s",
                message.getClass().toString(), message.toString());
        } else {
            handler.go(message);
        }
    }
}

जावा में टाइप क्लास के चर के उपयोग के बारे में अधिक जानकारी: http://docs.oracle.com/javase/tutorial/reflect/class/classNew.html


मामलों की एक छोटी संख्या के लिए (शायद किसी भी वास्तविक उदाहरण के लिए उन वर्गों की संख्या से अधिक) अगर-और नक्शे को बेहतर बना देगा, इसके अलावा सभी में हीप मेमोरी का उपयोग नहीं कर रहा है
idelvall


0

मैंने इस समस्या को हल कर लिया है reflection(लगभग 15 वर्ष पूर्व जेनरिक युग में)।

GenericClass object = (GenericClass) Class.forName(specificClassName).newInstance();

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

बेस क्लास सभी ठोस वर्गों में सामान्य स्थिति को परिभाषित करता है और कंक्रीट क्लास बेस क्लास में परिभाषित सार नियमों को ओवरराइड करके राज्य को संशोधित करेगा।

उस समय, मैं इस तंत्र का नाम नहीं जानता, जिसे इस रूप में जाना जाता है reflection

कुछ और विकल्प इस लेख में सूचीबद्ध हैं : Mapऔर enumप्रतिबिंब के अलावा।


बस जिज्ञासु, तुम GenericClassएक क्यों नहीं बना interface?
Ztyx

मेरे पास सामान्य स्थिति और डिफ़ॉल्ट व्यवहार था, जिसे कई संबंधित वस्तुओं
रवींद्र बाबू
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.