एक जावा वर्ग कैसे बनाया जाए जो एक इंटरफ़ेस को दो सामान्य प्रकारों के साथ लागू करता है?


164

मेरे पास एक सामान्य इंटरफ़ेस है

public interface Consumer<E> {
    public void consume(E e);
}

मेरे पास एक वर्ग है जो दो प्रकार की वस्तुओं का उपभोग करता है, इसलिए मैं कुछ ऐसा करना चाहूंगा:

public class TwoTypesConsumer implements Consumer<Tomato>, Consumer<Apple>
{
   public void consume(Tomato t) {  .....  }
   public void consume(Apple a) { ...... }
}

जाहिर है मैं ऐसा नहीं कर सकता।

मैं बेशक खुद प्रेषण लागू कर सकते हैं, उदाहरण के लिए

public class TwoTypesConsumer implements Consumer<Object> {
   public void consume(Object o) {
      if (o instanceof Tomato) { ..... }
      else if (o instanceof Apple) { ..... }
      else { throw new IllegalArgumentException(...) }
   }
}

लेकिन मैं कंपाइल-टाइम टाइप-चेकिंग और डिस्पैचिंग सॉल्यूशन की तलाश कर रहा हूं जो जेनेरिक प्रदान करते हैं।

सबसे अच्छा समाधान जो मैं सोच सकता हूं, वह है अलग इंटरफेस, जैसे

public interface AppleConsumer {
   public void consume(Apple a);
}

कार्यात्मक रूप से, यह समाधान ठीक है, मुझे लगता है। यह सिर्फ क्रिया और बदसूरत है।

कोई विचार?


आपको एक ही आधार रेखा के दो सामान्य इंटरफेस की आवश्यकता क्यों है?
एकरनोकड

6
टाइप इरेज़र के कारण आप ऐसा नहीं कर सकते। इसे दो अलग-अलग वर्गों में रखें जो उपभोक्ता को लागू करता है। अधिक छोटी कक्षाएं बनाता है, लेकिन आपके कोड को सामान्य रखता है (स्वीकृत उत्तर का उपयोग न करें, यह पूरी अवधारणा को तोड़ देता है ... आप TwoTypesConsumer को एक उपभोक्ता के रूप में नहीं मान सकते, जो कि BAD है)।
लुईस डायमंड

कार्यात्मक शैली के लिए इसे जांचें - stackoverflow.com/a/60466413/4121845
mano_ksp

जवाबों:


78

इनकैप्सुलेशन पर विचार करें:

public class TwoTypesConsumer {
    private TomatoConsumer tomatoConsumer = new TomatoConsumer();
    private AppleConsumer appleConsumer = new AppleConsumer();

    public void consume(Tomato t) { 
        tomatoConsumer.consume(t);
    }

    public void consume(Apple a) { 
        appleConsumer.consume(a);
    }

    public static class TomatoConsumer implements Consumer<Tomato> {
        public void consume(Tomato t) {  .....  }
    }

    public static class AppleConsumer implements Consumer<Apple> {
        public void consume(Apple a) {  .....  }
    }
}

यदि ये स्थिर आंतरिक वर्ग आपको परेशान करते हैं, तो आप अनाम कक्षाओं का उपयोग कर सकते हैं:

public class TwoTypesConsumer {
    private Consumer<Tomato> tomatoConsumer = new Consumer<Tomato>() {
        public void consume(Tomato t) {
        }
    };

    private Consumer<Apple> appleConsumer = new Consumer<Apple>() {
        public void consume(Apple a) {
        }
    };

    public void consume(Tomato t) {
        tomatoConsumer.consume(t);
    }

    public void consume(Apple a) {
        appleConsumer.consume(a);
    }
}

2
किसी भी तरह कि कोड डुप्लिकेट की तरह tome दिखता है ... मैं एक ही समस्या का सामना करना पड़ा और कोई अन्य समाधान नहीं मिला जो साफ दिखता है।
bln-tom

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

@JeffAxelrod मैं आंतरिक वर्गों को गैर-स्थिर बना देता हूँ, ताकि TwoTypesConsumerयदि आवश्यक हो, तो वे एन्क्लोज़िंग इंस्टेंस तक पहुँच प्राप्त कर सकें, और फिर आप twoTypesConsumer.getAppleConsumer()एक ऐसी विधि को पारित कर सकते हैं जो एक सेब उपभोक्ता चाहता है। एक अन्य विकल्प addConsumer(Producer<Apple> producer)TwoTypesConsumer के समान तरीके जोड़ना होगा ।
हरमन

यदि आप इंटरफ़ेस पर नियंत्रण नहीं रखते हैं तो यह काम नहीं करता है (उदाहरण के लिए cxf / rs ExceptionMapper) ...
vikingsteve

17
मैं इसे कहूँगा: यह जावा के साथ एक दोष है । पूरी तरह से कोई कारण नहीं है कि हमें एक ही इंटरफ़ेस के कई कार्यान्वयन करने की अनुमति नहीं दी जानी चाहिए, बशर्ते कि कार्यान्वयन अलग-अलग तर्क लेते हैं।
gromit190

41

प्रकार के क्षरण के कारण आप एक ही इंटरफ़ेस को दो बार (विभिन्न प्रकार के मापदंडों के साथ) लागू नहीं कर सकते।


6
मैं देख सकता हूं कि यह कैसे समस्या है ... सवाल तो यह है कि इस समस्या को दरकिनार करने का सबसे अच्छा (सबसे कुशल, सुरक्षित, सुरुचिपूर्ण) तरीका क्या है।
daphshez

2
व्यापार तर्क में जाने के बिना, यहाँ कुछ आगंतुक गंध की तरह 'बदबू आ रही है।
शिमि बंदिअल

12

यहाँ स्टीव मैकलियोड के आधार पर एक संभावित समाधान दिया गया है :

public class TwoTypesConsumer {
    public void consumeTomato(Tomato t) {...}
    public void consumeApple(Apple a) {...}

    public Consumer<Tomato> getTomatoConsumer() {
        return new Consumer<Tomato>() {
            public void consume(Tomato t) {
                consumeTomato(t);
            }
        }
    }

    public Consumer<Apple> getAppleConsumer() {
        return new Consumer<Apple>() {
            public void consume(Apple a) {
                consumeApple(t);
            }
        }
    }
}

सवाल का अंतर्निहित आवश्यकता थी Consumer<Tomato>और Consumer<Apple>है कि शेयर राज्य वस्तुओं। Consumer<Tomato>, Consumer<Apple>वस्तुओं की आवश्यकता अन्य तरीकों से आती है जो इन मापदंडों के रूप में अपेक्षा करते हैं। मुझे राज्य को साझा करने के लिए उन दोनों को लागू करने के लिए एक वर्ग की आवश्यकता है।

स्टीव का विचार दो आंतरिक वर्गों का उपयोग करना था, प्रत्येक एक अलग सामान्य प्रकार को लागू करते थे।

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


2
यदि कोई इसका उपयोग करता है: यह Consumer<*>उदाहरण के क्षेत्रों में उदाहरणों को संग्रहीत करने के लायक है यदि get*Consumerअक्सर कहा जाता है।
टीडब्लूआरआरओबी

7

कम से कम, आप निम्न की तरह कुछ करके अपने प्रेषण के कार्यान्वयन में एक छोटा सा सुधार कर सकते हैं:

public class TwoTypesConsumer implements Consumer<Fruit> {

फल टमाटर और सेब का पूर्वज है।


14
धन्यवाद, लेकिन जो कुछ भी कहता है, मैं टमाटर को फल नहीं मानता। दुर्भाग्य से वस्तु के अलावा कोई सामान्य आधार वर्ग नहीं है।
daphshez

2
आप हमेशा एक बेस क्लास बना सकते हैं, जिसका नाम है: AppleOrTomato;)
शिमि बंदिअल

1
बेहतर है, एक फल जोड़ें जो या तो सेब या टमाटर को दर्शाता है।
टॉम हॉल्टिन -

@Tom: जब तक आप यह नहीं कह रहे हैं कि गलतफहमी है, तो आपका सुझाव केवल समस्या को आगे बढ़ाता है, क्योंकि, फलों के लिए या तो Apple या टमाटर को सौंपने में सक्षम होने के लिए, फलों में Apple और टमाटर दोनों के लिए सुपरक्लास का एक क्षेत्र होना चाहिए। यह उस वस्तु को संदर्भित करता है जो इसे सौंपता है।
बुहब

1
इसका अर्थ यह होगा कि TwoTypesConsumer किसी भी प्रकार के फलों का उपभोग कर सकता है, किसी भी वर्तमान में लागू किया जा सकता है और भविष्य में कोई भी लागू कर सकता है।
टॉम गिलेन

3

बस इस पर ठोकर खाई। यह बस हुआ, कि मुझे एक ही समस्या थी, लेकिन मैंने इसे एक अलग तरीके से हल किया: मैंने बस इस तरह एक नया इंटरफ़ेस बनाया

public interface TwoTypesConsumer<A,B> extends Consumer<A>{
    public void consume(B b);
}

दुर्भाग्य से, यह सभी तर्क के खिलाफ के रूप में माना जाता है Consumer<A>और नहीं Consumer<B>। तो आपको अपने वर्ग के अंदर दूसरे उपभोक्ता के लिए एक छोटा एडेप्टर बनाना होगा

public class ConsumeHandler implements TwoTypeConsumer<A,B>{

    private final Consumer<B> consumerAdapter = new Consumer<B>(){
        public void consume(B b){
            ConsumeHandler.this.consume(B b);
        }
    };

    public void consume(A a){ //...
    }
    public void conusme(B b){ //...
    }
}

अगर Consumer<A>जरूरत है, तो आप बस पास कर सकते हैं this, और अगर Consumer<B>जरूरत है तो बस पास होने कीconsumerAdapter


Daphna का जवाब एक ही है, लेकिन क्लीनर और कम दृढ़ है।
ट्वीस्ट्रोब

1

आप इसे सीधे एक कक्षा में नहीं कर सकते क्योंकि सामान्य प्रकार और डुप्लिकेट इंटरफ़ेस घोषणा के उन्मूलन के कारण नीचे वर्ग परिभाषा को संकलित नहीं किया जा सकता है।

class TwoTypesConsumer implements Consumer<Apple>, Consumer<Tomato> { 
 // cannot compile
 ...
}

एक वर्ग में एक ही उपभोग के संचालन को पैक करने के लिए किसी अन्य समाधान के लिए आपकी कक्षा को परिभाषित करने की आवश्यकता है:

class TwoTypesConsumer { ... }

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

यह एक संकेतक भी हो सकता है कि 2 अलग-अलग वस्तुओं (यदि वे युग्मित नहीं हैं) का उपभोग करने के लिए एक वर्ग में बहुत अधिक जिम्मेदारी है।

हालाँकि, मैं क्या कर रहा हूँ और आप क्या कर सकते हैं स्पष्ट फैक्ट्री ऑब्जेक्ट को जोड़ने के लिए निम्नलिखित उपभोक्ताओं को निम्नलिखित तरीके से बनाना है:

interface ConsumerFactory {
     Consumer<Apple> createAppleConsumer();
     Consumer<Tomato> createTomatoConsumer();
}

यदि वास्तव में वे प्रकार वास्तव में युग्मित (संबंधित) हैं तो मैं इस तरह से एक कार्यान्वयन बनाने की सिफारिश करूंगा:

class TwoTypesConsumerFactory {

    // shared objects goes here

    private class TomatoConsumer implements Consumer<Tomato> {
        public void consume(Tomato tomato) {
            // you can access shared objects here
        }
    }

    private class AppleConsumer implements Consumer<Apple> {
        public void consume(Apple apple) {
            // you can access shared objects here
        }
    }


    // It is really important to return generic Consumer<Apple> here
    // instead of AppleConsumer. The classes should be rather private.
    public Consumer<Apple> createAppleConsumer() {
        return new AppleConsumer();
    }

    // ...and the same here
    public Consumer<Tomato> createTomatoConsumer() {
        return new TomatoConsumer();
    }
}

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

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

उस समाधान का नकारात्मक पहलू एक उच्च श्रेणी की जटिलता है (भले ही यह एक जावा फ़ाइल हो सकती है) और उपभोग की विधि तक पहुंचने के लिए आपको इसके बजाय एक और कॉल की आवश्यकता है:

twoTypesConsumer.consume(apple)
twoTypesConsumer.consume(tomato)

आपके पास:

twoTypesConsumerFactory.createAppleConsumer().consume(apple);
twoTypesConsumerFactory.createTomatoConsumer().consume(tomato);

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


1

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

हमारे कार्यात्मक इंटरफ़ेस इकाई का उपभोग करने के लिए

@FunctionalInterface
public interface Consumer<E> { 
     void consume(E e); 
}

हमारे प्रबंधक उचित रूप से इकाई को संसाधित और उपभोग करने के लिए

public class Manager {
    public <E> void process(Consumer<E> consumer, E entity) {
        consumer.consume(entity);
    }

    public void consume(Tomato t) {
        // Consume Tomato
    }

    public void consume(Apple a) {
        // Consume Apple
    }

    public void test() {
        process(this::consume, new Tomato());
        process(this::consume, new Apple());
    }
}

0

अधिक वर्गों के उपयोग से बचने के लिए एक और विकल्प। (उदाहरण java8 + का उपयोग करके)

// Mappable.java
public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

// TwoMappables.java
public interface TwoMappables {
    default Mappable<A> mapableA() {
         return new MappableA();
    }

    default Mappable<B> mapableB() {
         return new MappableB();
    }

    class MappableA implements Mappable<A> {}
    class MappableB implements Mappable<B> {}
}

// Something.java
public class Something implements TwoMappables {
    // ... business logic ...
    mapableA().mapTo(A);
    mapableB().mapTo(B);
}

0

पुराने सवालों के जवाब के लिए क्षमा करें, लेकिन मैं वास्तव में इसे प्यार करता हूँ! इस विकल्प को आज़माएं:

public class MegaConsumer implements Consumer<Object> {

  Map<Class, Consumer> consumersMap = new HashMap<>();
  Consumer<Object> baseConsumer = getConsumerFor(Object.class);

  public static void main(String[] args) {
    MegaConsumer megaConsumer = new MegaConsumer();
    
    //You can load your customed consumers
    megaConsumer.loadConsumerInMapFor(Tomato.class);
    megaConsumer.consumersMap.put(Apple.class, new Consumer<Apple>() {
        @Override
        public void consume(Apple e) {
            System.out.println("I eat an " + e.getClass().getSimpleName());
        }
    });
    
    //You can consume whatever
    megaConsumer.consume(new Tomato());
    megaConsumer.consume(new Apple());
    megaConsumer.consume("Other class");
  }

  @Override
  public void consume(Object e) {
    Consumer consumer = consumersMap.get(e.getClass());
    if(consumer == null) // No custom consumer found
      consumer = baseConsumer;// Consuming with the default Consumer<Object>
    consumer.consume(e);
  }

  private static <T> Consumer<T> getConsumerFor(Class<T> someClass){
    return t -> System.out.println(t.getClass().getSimpleName() + " consumed!");
  }

  private <T> Consumer<T> loadConsumerInMapFor(Class<T> someClass){
    return consumersMap.put(someClass, getConsumerFor(someClass));
  }
}

मेरे विचार में यही है, जिसकी तलाश में आप हैं।

आपको यह आउटपुट मिलता है:

टमाटर का सेवन!

मैं सेब खाता हूं

स्ट्रिंग भस्म!


प्रश्न में: "लेकिन मैं संकलन-समय-प्रकार की जाँच के लिए देख रहा हूँ ..."
एरैकोड

@aeracode कोई विकल्प नहीं है जो ओपी चाहता है। टाइप इरेज़र समान इंटरफ़ेस को दो बार अलग-अलग प्रकार के चर के साथ लागू करने के लिए अभेद्य बनाता है। मैं केवल आपको दूसरा रास्ता देने की कोशिश करता हूं। बेशक आप एक onbject उपभोग करने के लिए पहले से स्वीकार किए गए प्रकारों की जांच कर सकते हैं।
Awes0meM4n
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.