2009-08-19 8 views
129

J'ai une interface génériqueComment créer une classe Java qui implémente une interface avec deux types génériques?

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

J'ai une classe qui consomme deux types d'objets, donc je voudrais faire quelque chose comme:

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

Apparemment, je ne peux pas faire cela.

Je peux bien sûr mettre en œuvre la répartition moi-même, par ex. Mais je suis à la recherche de la solution de vérification de type et de répartition au moment de la compilation que fournissent les génériques.

La meilleure solution à laquelle je puisse penser est de définir des interfaces séparées, par ex.

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

Fonctionnellement, cette solution est OK, je pense. C'est juste verbeux et laid.

Des idées?

+0

Pourquoi avez-vous besoin de deux interfaces génériques du même type de base? – akarnokd

+5

En raison de l'effacement de type, vous ne pouvez pas faire cela. Gardez deux classes différentes qui implémente le consommateur. Fait plus de petites classes mais conserve votre code générique (N'utilisez pas la réponse acceptée, cela casse tout le concept ... vous ne pouvez pas traiter TwoTypesConsumer en tant que consommateur, ce qui est BAD). –

Répondre

67

Tenir compte encapsulation:

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

Si la création de ces classes internes statiques vous dérange, vous pouvez utiliser des classes anonymes:

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

en quelque sorte qui ressemble un peu à la duplication de code ... J'ai rencontré le même problème et trouvé aucune autre solution qui semble propre. –

+78

Mais 'TwoTypesConsumer' remplit les contrats * no *, alors à quoi ça sert? Il ne peut pas être passé à une méthode qui veut l'un ou l'autre type de 'Consumer'. L'idée générale d'un consommateur de deux types serait que vous pouvez le donner à une méthode qui veut un consommateur de tomate ainsi qu'une méthode qui veut un consommateur de pommes. Ici nous n'avons ni. –

+0

@JeffAxelrod Je rends les classes internes non statiques afin qu'elles aient accès à l'instance de 'TwoTypesConsumer 'qui l'entoure si nécessaire, et vous pouvez ensuite passer' twoTypesConsumer.getAppleConsumer() 'à une méthode qui veut un consommateur Apple. Une autre option consisterait à ajouter des méthodes similaires à 'addConsumer (Producer producer)' à TwoTypesConsumer. – herman

30

En raison de l'effacement de type, vous ne pouvez pas implémenter deux fois la même interface (avec des paramètres de type différents).

+5

Je peux voir comment c'est un problème ... la question est alors de savoir quel est le meilleur moyen (le plus efficace, sûr, élégant) de contourner ce problème. – daphshez

+1

Sans entrer dans la logique métier, quelque chose ici «sent» comme le modèle Visitor. –

7

Au moins, vous pouvez faire une petite amélioration à votre mise en œuvre d'expédition en faisant quelque chose comme ce qui suit:

public class TwoTypesConsumer implements Consumer<Fruit> { 

fruit est un ancêtre de tomate et Apple.

+10

Merci, mais quoi que disent les pros, je ne considère pas la tomate comme un fruit.Malheureusement, il n'y a pas de classe de base commune autre que Object. – daphshez

+1

Vous pouvez toujours créer une classe de base appelée: AppleOrTomato;) –

+1

Mieux, ajoutez un Fruit qui délègue à Apple ou Tomate. –

10

est ici une solution basée sur 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); 
      } 
     } 
    } 
} 

L'exigence implicite de la question était Consumer<Tomato> et Consumer<Apple> objets partageant l'état. Le besoin d'objets Consumer<Tomato>, Consumer<Apple> provient d'autres méthodes qui les attendent en tant que paramètres. J'ai besoin d'une classe les implémenter tous les deux afin de partager l'état.

L'idée de Steve était d'utiliser deux classes internes, chacune implémentant un type générique différent.

Cette version ajoute des accesseurs pour les objets qui implémentent l'interface Consumer, qui peuvent ensuite être transmis à d'autres méthodes les attendant.

+1

Si quelqu'un utilise ceci: il vaut la peine de stocker les instances 'Consumer <*>' dans les champs d'instance si 'get * Consumer' est souvent appelé. – TWiStErRob

3

juste tombé sur cela. Il vient d'arriver, que j'ai eu le même problème, mais je résolus d'une manière différente: Je viens de créer une nouvelle interface comme celui-ci

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

malheureusement, cela est considéré comme Consumer<A> et pas comme Consumer<B> contre toute logique .Donc, vous devez créer un petit adaptateur pour le deuxième consommateur comme celui-ci à l'intérieur de votre 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){ //... 
    } 
} 

si un Consumer<A> est nécessaire, vous pouvez tout simplement passer this, et si Consumer<B> est nécessaire juste passer consumerAdapter

+0

[Daphna] (http://stackoverflow.com/a/1298751/253468) La réponse est la même, mais plus propre et moins compliquée. – TWiStErRob

1

Vous ne pouvez pas faites-le directement dans une classe car la définition de classe ci-dessous ne peut pas être compilée en raison de l'effacement des types génériques et de la déclaration de l'interface en double.

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

Toute autre solution pour emballer la même consommer des opérations dans une classe nécessite de définir votre classe:

class TwoTypesConsumer { ... } 

qui est inutile que vous devez répéter/dupliquer la définition des deux opérations et ils ne sera pas référencé depuis l'interface. À mon humble avis, c'est une mauvaise duplication de code que j'essaie d'éviter.

Cela peut être aussi un indicateur qu'il y a trop de responsabilités dans une classe pour consommer 2 objets différents (s'ils ne sont pas couplés).

Cependant ce que je fais et ce que vous pouvez faire est d'ajouter explicitement objet usine pour créer les consommateurs connectés de la manière suivante:

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

Si, en réalité, ces types sont vraiment couplés (liés) alors je conseillerions à créer une mise en œuvre de telle manière:

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

l'avantage est que la classe usine connaît les deux implémentations, il y a un état partagé (si nécessaire) et vous pouvez retourner les consommateurs plus accouplées si nécessaire. Il n'y a pas de déclaration de méthode de consommation répétitive qui ne soit dérivée de l'interface.

Veuillez noter que chaque consommateur peut être indépendant (encore privé) s'il n'est pas complètement lié.

L'inconvénient de cette solution est une complexité de classe supérieure (même si cela peut être un fichier java) et d'accéder à consommer la méthode que vous besoin d'un plus appel donc au lieu de:

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

vous:

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

pour résumer, vous pouvez définir 2 consommateurs génériques dans une classe de niveau supérieur avec 2 classes internes mais en cas de besoin vous appeler pour obtenir d'abord une référence à la mise en œuvre appropriées consommateur car cela ne peut pas être simplement un objet de consommation.