web-dev-qa-db-fra.com

Interface générique

Supposons que je souhaite définir une interface qui représente un appel vers un service distant. Désormais, l'appel du service distant renvoie généralement quelque chose, mais peut également inclure des paramètres d'entrée. Supposons qu'une classe d'implémentation implémente généralement une seule méthode de service. Compte tenu des informations ci-dessus, la conception suivante est-elle médiocre (vous ne vous sentez pas bien):

public interface IExecutesService<A,B>
{
    public A executeService();
    public A executeService(B inputParameter);
}

Maintenant, supposons que j'implémente cette interface avec une classe qui exécute un service distant avec un paramètre d'entrée:

public class ServiceA implements IExecutesService<String,String>
{
  public String executeService()
  {
    //This service call should not be executed by this class
    throw new IllegalStateException("This method should not be called for this class...blabla");
  }

  public String executeService(String inputParameter)
  {
    //execute some service
  }

J'ai deux questions concernant ce qui précède:

  1. L’utilisation d’une interface générique (IExecutesService<A,B>) bon dans le cas où vous souhaitez fournir des sous-classes nécessitant différents paramètres d'entrée et types de retour pour les méthodes d'interface?
  2. Comment puis-je faire ce qui précède mieux? C'est à dire. Je souhaite regrouper mes exécuteurs de services sous une interface commune (IExecutesService); Cependant, une classe d'implémentation n'implémentera généralement qu'une des méthodes et l'utilisation d'une exception IllegalStateException est vraiment laide. De plus, le paramètre de type B dans IExecutesService<A,B> sera redondant pour une classe d'implémentation qui appelle un service sans paramètre d'entrée. Il semble également exagéré de créer deux interfaces distinctes pour les deux appels de service différents.
54
user192585

Voici une suggestion:

public interface Service<T,U> {
    T executeService(U... args);
}

public class MyService implements Service<String, Integer> {
    @Override
    public String executeService(Integer... args) {
        // do stuff
        return null;
    }
}

En raison du type gommé, toute classe ne pourra implémenter qu’un d’eux. Cela élimine au moins la méthode redondante.

Ce n'est pas une interface déraisonnable que vous proposez, mais je ne suis pas sûr à 100% de la valeur ajoutée. Vous voudrez peut-être simplement utiliser l'interface standard Callable . Il ne supporte pas les arguments mais cette partie de l'interface a la moindre valeur (imho).

84
cletus

Voici une autre suggestion:

public interface Service<T> {
   T execute();
}

en utilisant cette interface simple vous pouvez passer des arguments via le constructeur dans les classes de service concrètes:

public class FooService implements Service<String> {

    private final String input1;
    private final int input2;

    public FooService(String input1, int input2) {
       this.input1 = input1;
       this.input2 = input2;
    }

    @Override
    public String execute() {
        return String.format("'%s%d'", input1, input2);
    }
}
20
dfa

Je resterais avec deux interfaces différentes.

Vous avez dit que "Je veux regrouper mes exécuteurs de service sous une interface commune ... Il semble également exagéré de créer deux interfaces distinctes pour les deux appels de service différents ... Une classe implémentera une seule de ces interfaces"

On ne sait pas quelle est la raison d'avoir une seule interface alors. Si vous voulez l'utiliser comme marqueur, vous pouvez simplement exploiter les annotations.

Un autre point est qu’il peut arriver que vos exigences changent et que les méthodes avec une autre signature apparaissent à l’interface. Bien sûr, il est possible d'utiliser un modèle d'adaptateur à ce moment-là, mais il serait plutôt étrange de voir que cette classe implémente l'interface avec, par exemple, trois méthodes, dont deux d'entre elles déclenchent une exception UnsupportedOperationException. Il est possible que la quatrième méthode apparaisse, etc.

9
denis.zhdanov

En réponse strictement à votre question, je soutiens la proposition de cleytus.


Vous pouvez également utiliser une interface de marqueur (sans méthode), par exemple DistantCall, avec plusieurs sous-interfaces possédant les signatures précises souhaitées.

  • L'interface générale servirait à les marquer tous, au cas où vous voudriez écrire du code générique pour chacun d'eux.
  • Le nombre d'interfaces spécifiques peut être réduit en utilisant la signature générique de cleytus.

Exemples d'interfaces 'réutilisables':

    public interface DistantCall {
    }

    public interface TUDistantCall<T,U> extends DistantCall {
      T execute(U... us);
    }

    public interface UDistantCall<U> extends DistantCall {
      void execute(U... us);
    }

    public interface TDistantCall<T> extends DistantCall {
      T execute();
    }

    public interface TUVDistantCall<T, U, V> extends DistantCall {
      T execute(U u, V... vs);
    }
    ....

MIS À JOUR en réponse au commentaire de l'OP

Je ne pensais à aucune instance de l'appel . Je pensais que votre code d'appel savait ce qu'il appelait et qu'il vous suffisait d'assembler plusieurs appels distants dans une interface commune pour un code générique (, par exemple, l'audit de tous les appels distants, pour des raisons de performances ). Dans votre question, je n'ai vu aucune mention que le code d'appel soit générique :

Si c'est le cas, je suggère que vous n'ayez qu'une seule interface, qu'une seule signature. En avoir plusieurs n'apporterait que plus de complexité, pour rien.

Cependant, vous devez vous poser quelques questions plus larges:
--- (comment vous assurerez-vous que l'appelant et l'appelé communiquent correctement?

Cela pourrait être un suivi de cette question, ou une question différente ...

4
KLE

Si je comprends bien, vous souhaitez qu'une classe implémente plusieurs de ces interfaces avec des paramètres d'entrée/sortie différents? Cela ne fonctionnera pas en Java, car les génériques sont implémentés via l'effacement.

Le problème avec les génériques Java, c’est que les génériques ne sont en fait que de la magie du compilateur. Au moment de l’exécution, les classes ne conservent aucune information sur les types utilisés pour les éléments génériques (paramètres de type de classe, méthode paramètres de type, paramètres de type d'interface). Par conséquent, même si vous pouvez avoir des surcharges de méthodes spécifiques, vous ne pouvez pas les lier à plusieurs implémentations d'interface dont les paramètres de type génériques diffèrent.

En général, je comprends pourquoi vous pensez que ce code a une odeur. Cependant, afin de vous fournir une meilleure solution, il serait nécessaire d’en savoir un peu plus sur vos besoins. Pourquoi voulez-vous utiliser une interface générique en premier lieu?

3
Lucero