Costruttore in un'interfaccia?


149

So che non è possibile definire un costruttore in un'interfaccia. Ma mi chiedo perché, perché penso che potrebbe essere molto utile.

Quindi potresti essere sicuro che alcuni campi in una classe sono definiti per ogni implementazione di questa interfaccia.

Ad esempio, considerare la seguente classe di messaggi:

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

Se definisco un'interfaccia per questa classe in modo che io possa avere più classi che implementano l'interfaccia del messaggio, posso solo definire il metodo di invio e non il costruttore. Quindi, come posso garantire che ogni implementazione di questa classe abbia davvero un ricevitore impostato? Se uso un metodo come setReceiver(String receiver)questo, non posso essere sicuro che questo metodo sia veramente chiamato. Nel costruttore ho potuto assicurarlo.



2
Tu dici "Nel costruttore potrei assicurarmi [ogni implementazione di questa classe ha davvero un ricevitore impostato]." Ma no, non puoi assolutamente farlo. A condizione che fosse possibile definire un tale costruttore, il parametro sarebbe solo un forte suggerimento per i vostri implementatori, ma avrebbero potuto scegliere di ignorarlo semplicemente se lo avessero voluto.
Julien Silland,

3
@mattb Umm, questa è una lingua diversa.
yesennes,

Risposte:


130

Prendendo alcune delle cose che hai descritto:

"Quindi potresti essere sicuro che alcuni campi in una classe sono definiti per ogni implementazione di questa interfaccia."

"Se definisco un'interfaccia per questa classe in modo da poter avere più classi che implementano l'interfaccia del messaggio, posso solo definire il metodo di invio e non il costruttore"

... questi requisiti sono esattamente ciò a cui servono le classi astratte .


ma nota che il caso d'uso descritto da @Sebi (chiamare metodi sovraccarichi dai costruttori genitori) è una cattiva idea come spiegato nella mia risposta.
rsp,

44
Matt, questo è chiaramente vero, ma le classi astratte soffrono della limitazione della singola eredità, che porta le persone a guardare altri modi per specificare le gerarchie.
CPerkins,

6
Questo è vero e può risolvere il problema immediato di Sebi. Ma una ragione per usare le interfacce in Java è perché non puoi avere ereditarietà multiple. Nel caso in cui non riesca a rendere la mia "cosa" una classe astratta perché ho bisogno di ereditare da qualcos'altro, il problema rimane. Non che io sostenga di avere una soluzione.
Jay,

7
@CPerkins mentre questo è vero, non sto suggerendo che semplicemente usando una classe astratta risolverà il caso d'uso di Sebi. Semmai, è meglio dichiarare Messageun'interfaccia che definisce il send()metodo e se Sebi desidera fornire una classe "base" per le implementazioni Messagedell'interfaccia, quindi fornire anche una AbstractMessage. Le classi astratte non dovrebbero prendere il posto delle interfacce, non hanno mai tentato di suggerirlo.
opaco b

2
Capito, Matt. Non stavo litigando con te, più sottolineando che non è un sostituto completo di ciò che l'opera vuole.
CPerkins,

76

Un problema che si presenta quando si consentono ai costruttori di interfacce deriva dalla possibilità di implementare più interfacce contemporaneamente. Quando una classe implementa diverse interfacce che definiscono costruttori diversi, la classe dovrebbe implementare diversi costruttori, ciascuno soddisfacendo solo un'interfaccia, ma non le altre. Sarà impossibile costruire un oggetto che chiama ciascuno di questi costruttori.

O nel codice:

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 lingua non avrebbe potuto farlo permettendo cose comeclass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
mako,

1
Il significato più utile per un "costruttore in un'interfaccia", se consentito, sarebbe se new Set<Fnord>()potesse essere interpretato nel senso "Dammi qualcosa che posso usare come un Set<Fnord>"; se l'autore Set<T>intendesse HashSet<T>essere l'implementazione ideale per cose che non avevano un particolare bisogno di qualcos'altro, l'interfaccia che poteva definire new Set<Fnord>()poteva essere considerata sinonimo di new HashSet<Fnord>(). Per una classe implementare più interfacce non costituirebbe alcun problema, dal momento new InterfaceName()che semplicemente costruirebbe una classe designata dall'interfaccia .
supercat,

Contro-argomento: il tuo A(String,List)costruttore potrebbe essere il costruttore designato A(String)e A(List)potrebbe essere secondario a chiamarlo. Il tuo codice non è un controesempio, ma solo uno scarso.
Ben Leggiero,

Perché dovresti chiamare tutti i costruttori in un'implementazione ?! Sì, se implementa più interfacce con ctor, una con una stringa e una con un int, ha bisogno di due medici - ma entrambi o possono essere usati. Se ciò non è applicabile, la classe semplicemente non implementa entrambe le interfacce. E allora!? (ci sono altri motivi per non avere ctor nelle interfacce).
Kai,

@kai No, durante la costruzione di un'istanza è necessario chiamare entrambi i costruttori di interfacce. In altre parole: nel mio esempio, l'istanza ha sia un nome che un elenco, quindi ogni istanza deve creare un'istanza sia del nome che dell'elenco.
daniel kullmann,

13

Un'interfaccia definisce un contratto per un'API, ovvero un insieme di metodi concordati sia dall'implementatore che dall'utente dell'API. Un'interfaccia non ha un'implementazione istanziata, quindi nessun costruttore.

Il caso d'uso che descrivi è simile a una classe astratta in cui il costruttore chiama un metodo di un metodo astratto che viene implementato in una classe figlio.

Il problema intrinseco qui è che mentre il costruttore di base viene eseguito, l'oggetto figlio non è ancora stato costruito, e quindi in uno stato imprevedibile.

Riassumendo: sta chiedendo problemi quando chiami metodi sovraccarichi dai costruttori genitori, per citare mindprod :

In generale, è necessario evitare di chiamare qualsiasi metodo non definitivo in un costruttore. Il problema è che gli inizializzatori di istanza / l'inizializzazione delle variabili nella classe derivata vengono eseguiti dopo il costruttore della classe base.


6

Una soluzione che puoi provare è la definizione di un getInstance()metodo nella tua interfaccia in modo che l'implementatore sia consapevole di quali parametri devono essere gestiti. Non è solido come una classe astratta, ma consente una maggiore flessibilità come un'interfaccia.

Tuttavia, questa soluzione alternativa richiede di utilizzare l' getInstance()istanza di tutti gli oggetti di questa interfaccia.

Per esempio

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

5

Nell'interfaccia sono presenti solo campi statici che non è necessario inizializzare durante la creazione di oggetti in una sottoclasse e il metodo di interfaccia deve fornire l'implementazione effettiva in una sottoclasse. Quindi non è necessario il costruttore nell'interfaccia.

Secondo motivo: durante la creazione dell'oggetto della sottoclasse, viene chiamato il costruttore genitore. Ma se ci sarà più di un'interfaccia implementata, si verificherà un conflitto durante la chiamata del costruttore dell'interfaccia su quale costruttore del interfaccia chiamerà per primo


3

Se vuoi assicurarti che ogni implementazione dell'interfaccia contenga un campo specifico, devi semplicemente aggiungere alla tua interfaccia il getter per quel campo :

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • non romperà l'incapsulamento
  • farà sapere a tutti coloro che usano la tua interfaccia che l' Receiveroggetto deve essere passato in qualche modo alla classe (o dal costruttore o dal setter)

2

Le dipendenze a cui non si fa riferimento nei metodi di un'interfaccia devono essere considerate come dettagli di implementazione, non come qualcosa che l'interfaccia applica. Naturalmente ci possono essere delle eccezioni, ma di norma, è necessario definire l'interfaccia come quello che dovrebbe essere il comportamento. Lo stato interno di una determinata implementazione non dovrebbe essere un problema di progettazione dell'interfaccia.


1

Vedi questa domanda per il perché (tratto dai commenti).

Se hai davvero bisogno di fare qualcosa del genere, potresti volere una classe base astratta piuttosto che un'interfaccia.


1

Questo perché le interfacce non consentono di definire il corpo del metodo in esso. Ma dovremmo definire il costruttore nella stessa classe delle interfacce che hanno come modificatore astratto predefinito per tutti i metodi da definire. Ecco perché non possiamo definire il costruttore nelle interfacce.


0

Ecco un esempio usando questo Technic. In questo esempio specifico il codice sta effettuando una chiamata a Firebase usando una simulazione MyCompletionListenerche è un'interfaccia mascherata come una classe astratta, un'interfaccia con un costruttore

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

Come puoi utilizzare il privatemodificatore di accesso con interface?
Rudra,

0

Generalmente i costruttori servono per inizializzare membri non statici di una particolare classe rispetto all'oggetto.

Non esiste la creazione di oggetti per l'interfaccia in quanto vi sono solo metodi dichiarati ma non metodi definiti. Perché non possiamo creare oggetti con metodi dichiarati la creazione di is-object non è altro che l'allocazione di memoria (nella memoria heap) per i membri non statici.

JVM creerà memoria per i membri che sono completamente sviluppati e pronti per l'uso. In base a tali membri, JVM calcola la quantità di memoria richiesta per loro e crea memoria.

In caso di metodi dichiarati, JVM non è in grado di calcolare la quantità di memoria necessaria per questi metodi dichiarati poiché l'implementazione avverrà in futuro, cosa che non verrà eseguita entro questo momento. quindi la creazione di oggetti non è possibile per l'interfaccia.

conclusione:

senza la creazione di oggetti, non è possibile inizializzare membri non statici tramite un costruttore. Ecco perché il costruttore non è consentito all'interno di un'interfaccia (poiché non è possibile utilizzare il costruttore all'interno di un'interfaccia)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.