2009-08-19 8 views
129

Ho un'interfaccia genericaCome creare una classe Java che implementa un'interfaccia con due tipi generici?

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

ho una classe che consuma due tipi di oggetti, quindi mi piacerebbe fare qualcosa di simile:

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

A quanto pare non posso farlo.

Posso ovviamente implementare l'invio da solo, ad es.

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

Ma sto cercando la soluzione di controllo del tipo di compilazione in tempo reale fornita dai generici.

La soluzione migliore che posso pensare è definire interfacce separate, ad es.

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

Funzionalmente, questa soluzione è OK, penso. È solo verboso e brutto.

Qualche idea?

+0

Perché sono necessarie due interfacce generiche dello stesso tipo di base? – akarnokd

+5

A causa della cancellazione del tipo non è possibile farlo. Tenerlo in due classi diverse che implementa il consumatore. Rende più piccole classi ma mantiene generico il codice (non usare la risposta accettata, rompe l'intero concetto ... non puoi trattare il TwoTypesConsumer come un consumatore, che è BAD). –

risposta

67

Considerare l'incapsulamento:

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) { ..... } 
    } 
} 

Se la creazione di queste classi interne statiche ti dà fastidio, è possibile utilizzare le classi anonime:

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); 
    } 
} 
+1

in qualche modo sembra tome come la duplicazione del codice ... Ho riscontrato lo stesso problema e non ho trovato nessuna altra soluzione che sembra pulita. –

+78

Ma 'TwoTypesConsumer' soddisfa * no * contratti, quindi qual è il punto? Non può essere passato a un metodo che vuole un tipo di 'Consumer'. L'idea di un consumatore di due tipi è che puoi darlo a un metodo che vuole un consumatore di pomodoro e un metodo che vuole un consumatore di mele. Qui non abbiamo né –

+0

@JeffAxelrod Vorrei rendere le classi interne non statiche in modo che abbiano accesso all'istanza di 'TwoTypesConsumer' che racchiude se necessario, e quindi puoi passare' twoTypesConsumer.getAppleConsumer() 'a un metodo che vuole un consumatore di apple. Un'altra opzione sarebbe aggiungere metodi simili a 'addConsumer (produttore produttore)' a TwoTypesConsumer. – herman

30

A causa della cancellazione dei tipi, non è possibile implementare la stessa interfaccia due volte (con diversi parametri di tipo).

+5

Posso vedere come è un problema ... la domanda è quindi qual è il modo migliore (più efficiente, sicuro, elegante) per aggirare questo problema. – daphshez

+1

Senza entrare nella logica aziendale, qualcosa qui "odora" come il pattern Visitor. –

7

Almeno, si può fare un piccolo miglioramento per l'implementazione di spedizione facendo qualcosa di simile al seguente:

public class TwoTypesConsumer implements Consumer<Fruit> { 

Frutta essere un antenato di pomodoro e Apple.

+10

Grazie, ma qualunque cosa dicono i professionisti, non considero il pomodoro come frutto.Sfortunatamente non esiste una classe base comune diversa da Object. – daphshez

+1

Puoi sempre creare una classe base chiamata: AppleOrTomato;) –

+1

Meglio, aggiungi un frutto che delega a Apple o a Pomodoro. –

10

Ecco una possibile soluzione basata su Steve McLeod's one:

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); 
      } 
     } 
    } 
} 

Il requisito implicito della domanda era Consumer<Tomato> e Consumer<Apple> oggetti che condividono lo stato. La necessità di oggetti Consumer<Tomato>, Consumer<Apple> deriva da altri metodi che si aspettano questi come parametri. Ho bisogno di una classe per implementarli entrambi allo scopo di condividere lo stato.

L'idea di Steve era di utilizzare due classi interne, ognuna delle quali implementava un diverso tipo generico.

Questa versione aggiunge getter per gli oggetti che implementano l'interfaccia Consumer, che possono essere passati ad altri metodi che li prevedono.

+1

Se qualcuno lo utilizza: vale la pena memorizzare le istanze 'Consumer <*>' nei campi di istanza se 'get * Consumer' viene chiamato spesso. – TWiStErRob

3

appena inciampato su questo. E 'appena successo, che ho avuto lo stesso problema, ma ho risolto in un modo diverso: Ho appena creato una nuova interfaccia come questo

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

purtroppo, questo è considerato come Consumer<A> e NON come Consumer<B> contro ogni logica .Quindi devi creare un piccolo adattatore per il secondo consumatore come questo all'interno della vostra classe

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){ //... 
    } 
} 

se è necessaria una Consumer<A>, si può semplicemente passare this, e se è necessario Consumer<B> basta passare consumerAdapter

+0

[Daphna] (http://stackoverflow.com/a/1298751/253468) La risposta è la stessa, ma più pulita e meno complicata. – TWiStErRob

1

Non è possibile fatelo direttamente in una classe, poiché la definizione di classe seguente non può essere compilata a causa della cancellazione di tipi generici e di dichiarazioni di interfacce duplicate.

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

Qualsiasi altra soluzione per l'imballaggio lo stesso consumare le operazioni in una classe richiede di definire la classe come:

class TwoTypesConsumer { ... } 

che è inutile come è necessario ripetere/duplicare la definizione di entrambe le operazioni e che non sarà referenziato dall'interfaccia. Farlo è una piccola e cattiva duplicazione del codice che sto cercando di evitare.

Questo potrebbe essere un indicatore anche che c'è troppa responsabilità in una classe di consumare 2 oggetti diversi (se non sono accoppiati).

Tuttavia quello che sto facendo e cosa si può fare è aggiungere oggetto factory esplicito per creare utenze collegate nel seguente modo:

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

Se in realtà questi tipi sono davvero accoppiati (legati) poi ho lo consiglierei a creare un'implementazione in tal modo:

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(); 
    } 
} 

il vantaggio è che la classe factory conosce entrambe le implementazioni, v'è uno stato condiviso (se necessario) e si può tornare ai consumatori più accoppiate, se necessario. Non esiste alcuna dichiarazione del metodo di consumo che non sia derivata dall'interfaccia.

Si prega di notare che ogni consumatore potrebbe essere indipendente (ancora privato) di classe se non sono completamente correlati.

Lo svantaggio di questa soluzione è una complessità di classe superiore (anche se questo può essere un unico file java) e per accedere consumare metodo avete bisogno di uno più chiamata così, invece di:

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

avete:

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

in sintesi si può definire 2 consumatori generici in una classe di livello superiore con 2 classi interne ma in caso di chiamate è necessario ottenere prima un riferimento di appropriarsi attuazione consumatore in quanto questo non può essere semplicemente un oggetto consumatore.

Problemi correlati