Constructeur dans une interface?


149

Je sais qu'il n'est pas possible de définir un constructeur dans une interface. Mais je me demande pourquoi, car je pense que cela pourrait être très utile.

Vous pouvez donc être sûr que certains champs d'une classe sont définis pour chaque implémentation de cette interface.

Par exemple, considérez la classe de message suivante:

public class MyMessage {

   public MyMessage(String receiver) {
      this.receiver = receiver;
   }

   private String receiver;

   public void send() {
      //some implementation for sending the mssage to the receiver
   }
}

Si vous définissez une interface pour cette classe afin que je puisse avoir plus de classes qui implémentent l'interface de message, je ne peux définir que la méthode d'envoi et non le constructeur. Alors, comment puis-je m'assurer que chaque implémentation de cette classe a vraiment un ensemble de récepteurs? Si j'utilise une méthode comme celle-ci, setReceiver(String receiver)je ne peux pas être sûr que cette méthode est vraiment appelée. Dans le constructeur, je pourrais le garantir.



2
Vous dites "Dans le constructeur, je pourrais m'assurer que [chaque implémentation de cette classe a vraiment un ensemble de récepteurs]." Mais non, vous ne pourriez pas faire ça. Pourvu qu'il soit possible de définir un tel constructeur, le paramètre ne serait qu'un indice fort pour vos implémenteurs - mais ils pourraient choisir de simplement l'ignorer s'ils le voulaient.
Julien Silland

3
@mattb Umm, c'est une langue différente.
yesennes

Réponses:


130

Prenant certaines des choses que vous avez décrites:

"Vous pouvez donc être sûr que certains champs d'une classe sont définis pour chaque implémentation de cette interface."

"Si vous définissez une interface pour cette classe afin que je puisse avoir plus de classes qui implémentent l'interface de message, je ne peux définir que la méthode d'envoi et non le constructeur"

... ces exigences sont exactement à quoi servent les classes abstraites .


mais notez que le cas d'utilisation décrit par @Sebi (appel de méthodes surchargées à partir de constructeurs parents) est une mauvaise idée, comme expliqué dans ma réponse.
rsp

44
Matt, c'est clairement vrai, mais les classes abstraites souffrent de la limitation de l'héritage unique, ce qui amène les gens à regarder d'autres façons de spécifier des hiérarchies.
CPerkins

6
Cela est vrai et peut résoudre le problème immédiat de Sebi. Mais une des raisons d'utiliser les interfaces en Java est que vous ne pouvez pas avoir d'héritage multiple. Dans un cas où je ne peux pas faire de mon "objet" une classe abstraite parce que j'ai besoin d'hériter de quelque chose d'autre, le problème demeure. Non pas que je prétende avoir une solution.
Jay

7
@CPerkins bien que cela soit vrai, je ne suggère pas que la simple utilisation d'une classe abstraite résoudra le cas d'utilisation de Sebi. Si quoi que ce soit, il est préférable de déclarer une Messageinterface qui définit la send()méthode, et si Sebi souhaite fournir une classe "de base" pour les implémentations de l' Messageinterface, alors fournissez une AbstractMessageaussi. Les classes abstraites ne devraient pas remplacer les interfaces, n'a jamais tenté de le suggérer.
matt b

2
Compris, Matt. Je ne me disputais pas avec vous, soulignant davantage que ce n'était pas un remplacement complet pour ce que veut l'op.
CPerkins

76

Un problème que vous rencontrez lorsque vous autorisez les constructeurs dans les interfaces vient de la possibilité d'implémenter plusieurs interfaces en même temps. Lorsqu'une classe implémente plusieurs interfaces qui définissent différents constructeurs, la classe devrait implémenter plusieurs constructeurs, chacun satisfaisant une seule interface, mais pas les autres. Il sera impossible de construire un objet qui appelle chacun de ces constructeurs.

Ou en code:

interface Named { Named(String name); }
interface HasList { HasList(List list); }

class A implements Named, HasList {

  /** implements Named constructor.
   * This constructor should not be used from outside, 
   * because List parameter is missing
   */
  public A(String name)  { 
    ...
  }

  /** implements HasList constructor.
   * This constructor should not be used from outside, 
   * because String parameter is missing
   */
  public A(List list) {
    ...
  }

  /** This is the constructor that we would actually 
   * need to satisfy both interfaces at the same time
   */ 
  public A(String name, List list) {
    this(name);
    // the next line is illegal; you can only call one other super constructor
    this(list); 
  }
}

1
La langue n'aurait pas pu le faire en autorisant des choses commeclass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
mako

1
La signification la plus utile pour un "constructeur dans une interface", si elle est autorisée, serait si elle new Set<Fnord>()pouvait être interprétée comme signifiant "Donnez-moi quelque chose que je peux utiliser comme Set<Fnord>"; si l'auteur de Set<T>avait l'intention HashSet<T>d'être l'implémentation de référence pour des choses qui n'avaient pas besoin de quelque chose d'autre, l'interface pourrait alors définir new Set<Fnord>()pourrait être considérée comme synonyme de new HashSet<Fnord>(). Pour une classe, implémenter plusieurs interfaces ne poserait aucun problème, car il new InterfaceName()faudrait simplement construire une classe désignée par l'interface .
supercat

Contre-argument: votre A(String,List)constructeur pourrait être le constructeur désigné, A(String)et A(List)pourrait être des constructeurs secondaires qui l'appellent. Votre code n'est pas un contre-exemple, juste un mauvais.
Ben Leggiero

Pourquoi appelleriez-vous tous les constructeurs d'une implémentation?! Oui, s'il implémente plus d'interfaces avec ctor, une avec une chaîne et une avec un int, il a besoin de deux ctors - mais l'un ou l'autre peut être utilisé. Si cela n'est pas applicable, la classe n'implémente tout simplement pas les deux interfaces. Et alors!? (il y a cependant d'autres raisons pour ne pas avoir de ctor dans les interfaces).
kai

@kai Non, il doit appeler les deux constructeurs d'interface lors de la construction d'une instance. En d'autres termes: dans mon exemple, l'instance a à la fois un nom et une liste, donc chaque instance doit instancier à la fois le nom et la liste.
daniel kullmann

13

Une interface définit un contrat pour une API, c'est-à-dire un ensemble de méthodes sur lesquelles le réalisateur et l'utilisateur de l'API s'accordent. Une interface n'a pas d'implémentation instanciée, donc pas de constructeur.

Le cas d'utilisation que vous décrivez s'apparente à une classe abstraite dans laquelle le constructeur appelle une méthode d'une méthode abstraite qui est implémentée dans une classe enfant.

Le problème inhérent ici est que pendant que le constructeur de base est en cours d'exécution, l'objet enfant n'est pas encore construit, et donc dans un état imprévisible.

Pour résumer: cela pose-t-il des problèmes lorsque vous appelez des méthodes surchargées à partir de constructeurs parents, pour citer mindprod :

En général, vous devez éviter d'appeler des méthodes non finales dans un constructeur. Le problème est que les initialiseurs d'instance / l'initialisation des variables dans la classe dérivée sont exécutés après le constructeur de la classe de base.


6

Une solution que vous pouvez essayer consiste à définir un getInstance() méthode dans votre interface afin que l'implémenteur sache quels paramètres doivent être gérés. Ce n'est pas aussi solide qu'une classe abstraite, mais cela permet plus de flexibilité en tant qu'interface.

Toutefois, cette solution de contournement nécessite que vous getInstance()utilisiez pour instancier tous les objets de cette interface.

Par exemple

public interface Module {
    Module getInstance(Receiver receiver);
}

5

Il n'y a que des champs statiques dans l'interface qui n'ont pas besoin d'être initialisés lors de la création d'objet dans la sous-classe et la méthode d'interface doit fournir une implémentation réelle dans la sous-classe. Il n'y a donc pas besoin de constructeur dans l'interface.

Deuxième raison - lors de la création de l'objet de la sous-classe, le constructeur parent est appelé .Mais s'il y a plus d'une interface implémentée, un conflit se produira lors de l'appel du constructeur d'interface quant à savoir quel constructeur d'interface appellera en premier


3

Si vous voulez vous assurer que chaque implémentation de l'interface contient un champ spécifique, il vous suffit d'ajouter à votre interface le getter pour ce champ :

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • ça ne cassera pas l'encapsulation
  • il fera savoir à tous ceux qui utilisent votre interface que l' Receiverobjet doit être passé à la classe d'une manière ou d'une autre (soit par constructeur, soit par setter)

2

Les dépendances qui ne sont pas référencées dans une méthode d'interface doivent être considérées comme des détails d'implémentation, et non comme quelque chose que l'interface applique. Bien sûr, il peut y avoir des exceptions, mais en règle générale, vous devez définir votre interface comme le comportement attendu. L'état interne d'une implémentation donnée ne devrait pas être une préoccupation de conception de l'interface.


1

Voir cette question pour le pourquoi (tiré des commentaires).

Si vous avez vraiment besoin de faire quelque chose comme ça, vous voudrez peut-être une classe de base abstraite plutôt qu'une interface.


1

Ceci est dû au fait que les interfaces ne permettent pas de définir le corps de la méthode, mais nous devrions avoir à définir le constructeur dans la même classe que les interfaces ont par défaut un modificateur abstrait pour toutes les méthodes à définir. C'est pourquoi nous ne pouvons pas définir de constructeur dans les interfaces.


0

Voici un exemple utilisant cette technique. Dans cet exemple spécifique, le code fait un appel à Firebase en utilisant une maquette MyCompletionListenerqui est une interface masquée comme une classe abstraite, une interface avec un constructeur

private interface Listener {
    void onComplete(databaseError, databaseReference);
}

public abstract class MyCompletionListener implements Listener{
    String id;
    String name;
    public MyCompletionListener(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

private void removeUserPresenceOnCurrentItem() {
    mFirebase.removeValue(child("some_key"), new MyCompletionListener(UUID.randomUUID().toString(), "removeUserPresenceOnCurrentItem") {
        @Override
        public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {

        }
    });
    }
}

@Override
public void removeValue(DatabaseReference ref, final MyCompletionListener var1) {
    CompletionListener cListener = new CompletionListener() {
                @Override
                public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                    if (var1 != null){
                        System.out.println("Im back and my id is: " var1.is + " and my name is: " var1.name);
                        var1.onComplete(databaseError, databaseReference);
                    }
                }
            };
    ref.removeValue(cListener);
}

Comment pouvez-vous utiliser le privatemodificateur d'accès avec interface?
Rudra

0

Généralement, les constructeurs servent à initialiser les membres non statiques d'une classe particulière par rapport à un objet.

Il n'y a pas de création d'objet pour l'interface car il n'y a que des méthodes déclarées mais pas des méthodes définies. Pourquoi nous ne pouvons pas créer d'objet pour les méthodes déclarées La création est-objet n'est rien d'autre que l'allocation de mémoire (dans la mémoire du tas) pour les membres non statiques.

JVM créera de la mémoire pour les membres qui sont entièrement développés et prêts à l'emploi. Sur la base de ces membres, JVM calcule la quantité de mémoire nécessaire pour eux et crée de la mémoire.

En cas de méthodes déclarées, JVM est incapable de calculer la quantité de mémoire requise pour ces méthodes déclarées car l'implémentation se fera à l'avenir, ce qui n'est pas fait à ce moment-là. donc la création d'objet n'est pas possible pour l'interface.

conclusion:

sans création d'objet, il n'y a aucune chance d'initialiser les membres non statiques via un constructeur, c'est pourquoi le constructeur n'est pas autorisé à l'intérieur d'une interface (car il n'y a pas d'utilisation de constructeur dans une interface)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.