Java - Collisione del nome del metodo nell'implementazione dell'interfaccia


88

Se ho due interfacce, entrambe abbastanza diverse nei loro scopi, ma con la stessa firma del metodo, come faccio a fare in modo che una classe le implementi entrambe senza essere costretto a scrivere un unico metodo che serva per entrambe le interfacce e scrivendo una logica contorta nel metodo implementazione che controlla per quale tipo di oggetto viene effettuata la chiamata e invoca il codice corretto?

In C #, questo viene superato da quella che viene chiamata implementazione dell'interfaccia esplicita. Esiste un modo equivalente in Java?


37
Quando una classe deve implementare due metodi con la stessa firma che fanno cose diverse , allora quasi certamente la tua classe sta facendo troppe cose.
Joachim Sauer

15
Quanto sopra potrebbe non essere sempre vero IMO. A volte, in una singola classe, hai bisogno di metodi che devono confermare un contratto esterno (vincolando quindi le firme), ma che hanno implementazioni diverse. In effetti, questi sono requisiti comuni quando si progetta una classe non banale. Il sovraccarico e l'override sono necessariamente meccanismi per consentire metodi che fanno cose diverse che potrebbero non differire nella firma, o differire leggermente. Quello che ho qui è solo un po 'più restrittivo in quanto non consente la sottoclasse / e non consente nemmeno minima variazione sulle firme.
Bhaskar

1
Sarei curioso di sapere quali sono queste classi e metodi.
Uri

2
Ho riscontrato un caso del genere in cui una classe legacy "Address" implementava interfacce Person e Firm che avevano un metodo getName () semplicemente restituendo una stringa dal modello di dati. Un nuovo requisito aziendale specificava che Person.getName () restituisse una stringa formattata come "Cognome, nomi dati". Dopo molte discussioni, i dati sono stati invece riformattati nel database.
belwood

12
Il solo fatto di affermare che la classe sta quasi certamente facendo troppe cose NON è COSTRUTTIVO. Ho proprio questo caso proprio ora che la mia classe ha collisioni di nomi di metodi da 2 interfacce diverse e la mia classe NON sta facendo troppe cose. Gli scopi sono abbastanza simili, ma fanno cose leggermente diverse. Non cercare di difendere un linguaggio di programmazione ovviamente gravemente handicappato accusando l'interlocutore di implementare un cattivo design del software!
j00hi

Risposte:


75

No, non è possibile implementare lo stesso metodo in due modi diversi in una classe in Java.

Ciò può portare a molte situazioni confuse, motivo per cui Java non lo ha consentito.

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

class Impl implements ISomething, ISomething2 {
   void doSomething() {} // There can only be one implementation of this method.
}

Quello che puoi fare è comporre una classe da due classi che implementano ciascuna un'interfaccia diversa. Quindi quella classe avrà il comportamento di entrambe le interfacce.

class CompositeClass {
    ISomething class1;
    ISomething2 class2;
    void doSomething1(){class1.doSomething();}
    void doSomething2(){class2.doSomething();}
}

9
Ma in questo modo, non posso passare un'istanza di CompositeClass da qualche parte in cui è previsto un riferimento delle interfacce (ISomething o ISomething2)? Non posso nemmeno aspettarmi che il codice client sia in grado di eseguire il cast della mia istanza all'interfaccia appropriata, quindi non sto perdendo qualcosa a causa di questa restrizione? Si noti inoltre che in questo modo, quando si scrivono classi che implementano effettivamente le rispettive interfacce, si perde il vantaggio di avere il codice in una singola classe, il che a volte può essere un serio impedimento.
Bhaskar

9
@ Bhaskar, dai punti validi. Il miglior consiglio che ho è aggiungere un metodo ISomething1 CompositeClass.asInterface1();and ISomething2 CompositeClass.asInterface2();a quella classe. Quindi puoi semplicemente far uscire l'uno o l'altro dalla classe composita. Tuttavia, non esiste una grande soluzione a questo problema.
jjnguy

1
Parlando delle situazioni confuse a cui ciò può portare, puoi fare un esempio? Non possiamo pensare al nome dell'interfaccia aggiunto al nome del metodo come una risoluzione extra dell'ambito che può quindi evitare la collisione / confusione?
Bhaskar

@Bhaskar È meglio se le nostre classi aderiscono al principio di responsabilità unica. Se esiste una classe che implementa due interfacce molto diverse penso che il design dovrebbe essere rielaborato per dividere le classi per prendersi cura della singola responsabilità.
Anirudhan J

1
Quanto sarebbe confuso, davvero, consentire qualcosa come public long getCountAsLong() implements interface2.getCount {...}[nel caso in cui l'interfaccia richieda un longma gli utenti della classe si aspettano int] o private void AddStub(T newObj) implements coolectionInterface.Add[supponendo che collectionInterfaceabbia un canAdd()metodo, e per tutte le istanze di questa classe restituisce false]?
supercat

13

Non esiste un vero modo per risolvere questo problema in Java. Potresti usare le classi interne come soluzione alternativa:

interface Alfa { void m(); }
interface Beta { void m(); }
class AlfaBeta implements Alfa {
    private int value;
    public void m() { ++value; } // Alfa.m()
    public Beta asBeta() {
        return new Beta(){
            public void m() { --value; } // Beta.m()
        };
    }
}

Sebbene non consenta lanci da AlfaBetaa Beta, i downcast sono generalmente malvagi, e se ci si può aspettare che Alfaun'istanza abbia spesso anche un Betaaspetto, e per qualche ragione (di solito l'ottimizzazione è l'unica ragione valida) vuoi essere in grado per convertirlo in Beta, potresti creare una sottointerfaccia Alfacon Beta asBeta()al suo interno.


Intendi una classe anonima piuttosto che una classe interna?
Zaid Masud

2
@ZaidMasud intendo le classi interne, poiché possono accedere allo stato privato dell'oggetto che lo racchiude). Naturalmente anche queste classi interne possono essere anonime.
gustafc

11

Se stai riscontrando questo problema, è molto probabile che tu stia usando l' ereditarietà dove dovresti usare la delega . Se è necessario fornire due interfacce diverse, anche se simili, per lo stesso modello di dati sottostante, è necessario utilizzare una vista per fornire l'accesso ai dati in modo economico utilizzando un'altra interfaccia.

Per fornire un esempio concreto per quest'ultimo caso, supponiamo di voler implementare sia Collectione MyCollection(che non eredita da Collectione ha un'interfaccia incompatibile). È possibile fornire a Collection getCollectionView()e MyCollection getMyCollectionView()funzioni che forniscono un'implementazione leggera di Collectione MyCollection, utilizzando gli stessi dati sottostanti.

Per il primo caso ... supponi di volere davvero un array di numeri interi e un array di stringhe. Invece di ereditare da entrambi List<Integer>e List<String>, dovresti avere un membro di tipo List<Integer>e un altro membro di tipo List<String>e fare riferimento a quei membri, piuttosto che provare a ereditare da entrambi. Anche se hai bisogno solo di un elenco di numeri interi, in questo caso è meglio usare la composizione / delega sull'ereditarietà.


Non credo proprio. Stai dimenticando le librerie che richiedono di implementare interfacce diverse per essere compatibili con loro. Puoi imbatterti in questo utilizzando più librerie in conflitto molto più spesso di quanto ti imbatti nel tuo codice.
piscina notturna

1
@nightpool se si utilizzano più librerie che richiedono ciascuna interfacce diverse, non è ancora necessario che un singolo oggetto implementi entrambe le interfacce; è possibile fare in modo che l'oggetto disponga di funzioni di accesso per restituire ciascuna delle due diverse interfacce (e chiamare la funzione di accesso appropriata quando si passa l'oggetto a una delle librerie sottostanti).
Michael Aaron Safyan,

1

Il problema "classico" di Java riguarda anche il mio sviluppo Android ...
Il motivo sembra essere semplice:
più framework / librerie devi usare, più facilmente le cose possono essere fuori controllo ...

Nel mio caso, ho una classe BootStrapperApp ereditato da android.app.Application ,
mentre la stessa classe dovrebbe implementare anche un'interfaccia Platform di un framework MVVM per integrarsi.
La collisione del metodo si è verificata su un metodo getString () , che viene annunciato da entrambe le interfacce e dovrebbe avere un'implementazione differenet in contesti diversi.
La soluzione alternativa (brutta..IMO) è utilizzare una classe interna per implementare tutta la piattaformametodi, solo a causa di un conflitto minore di firma del metodo ... in alcuni casi, tale metodo preso in prestito non viene nemmeno utilizzato (ma influisce sulla semantica di progettazione principale).
Tendo a concordare che l'indicazione esplicita del contesto / spazio dei nomi in stile C # sia utile.


1
Non ho mai realizzato quanto C # fosse ponderato e ricco di funzionalità fino a quando non ho iniziato a utilizzare Java per lo sviluppo Android. Ho dato per scontate quelle funzionalità di C #. Java non dispone di troppe funzionalità.
Damn Vegetables

1

L'unica soluzione che mi è venuta in mente è utilizzare oggetti riferiti a quello che si desidera impiantare più interfacce.

es: supponendo di avere 2 interfacce da implementare

public interface Framework1Interface {

    void method(Object o);
}

e

public interface Framework2Interface {
    void method(Object o);
}

puoi racchiuderli in due oggetti Facador:

public class Facador1 implements Framework1Interface {

    private final ObjectToUse reference;

    public static Framework1Interface Create(ObjectToUse ref) {
        return new Facador1(ref);
    }

    private Facador1(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework1Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork1(o);
    }
}

e

public class Facador2 implements Framework2Interface {

    private final ObjectToUse reference;

    public static Framework2Interface Create(ObjectToUse ref) {
        return new Facador2(ref);
    }

    private Facador2(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework2Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork2(o);
    }
}

Alla fine la classe che volevi dovrebbe qualcosa di simile

public class ObjectToUse {

    private Framework1Interface facFramework1Interface;
    private Framework2Interface facFramework2Interface;

    public ObjectToUse() {
    }

    public Framework1Interface getAsFramework1Interface() {
        if (facFramework1Interface == null) {
            facFramework1Interface = Facador1.Create(this);
        }
        return facFramework1Interface;
    }

    public Framework2Interface getAsFramework2Interface() {
        if (facFramework2Interface == null) {
            facFramework2Interface = Facador2.Create(this);
        }
        return facFramework2Interface;
    }

    public void methodForFrameWork1(Object o) {
    }

    public void methodForFrameWork2(Object o) {
    }
}

ora puoi usare i metodi getAs * per "esporre" la tua classe


0

È possibile utilizzare un pattern Adapter per farli funzionare. Crea due adattatori per ciascuna interfaccia e usali. Dovrebbe risolvere il problema.


-1

Tutto bene quando hai il controllo totale su tutto il codice in questione e puoi implementarlo in anticipo. Ora immagina di avere una classe pubblica esistente usata in molti posti con un metodo

public class MyClass{

    private String name;

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

Ora devi passarlo al WizzBangProcessor standard che richiede classi per implementare WBPInterface ... che ha anche un metodo getName (), ma invece della tua implementazione concreta, questa interfaccia si aspetta che il metodo restituisca il nome di un tipo di Wizz Bang Processing.

In C # sarebbe un trvial

public class MyClass : WBPInterface{

    private String name;

    String WBPInterface.getName(){
        return "MyWizzBangProcessor";
    }

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

In Java Tough dovrai identificare ogni punto della base di codice distribuita esistente in cui devi convertire da un'interfaccia all'altra. Sicuramente la società WizzBangProcessor avrebbe dovuto usare getWizzBangProcessName (), ma anche loro sono sviluppatori. Nel loro contesto getName andava bene. In realtà, al di fuori di Java, la maggior parte degli altri linguaggi basati su OO lo supportano. Java è raro che imponga l'implementazione di tutte le interfacce con lo stesso metodo NAME.

La maggior parte degli altri linguaggi ha un compilatore che è più che felice di ricevere un'istruzione per dire "questo metodo in questa classe che corrisponde alla firma di questo metodo in questa interfaccia implementata è la sua implementazione". Dopo tutto, il punto centrale della definizione delle interfacce è consentire l'astrazione della definizione dall'implementazione. (Non farmi nemmeno iniziare ad avere metodi predefiniti nelle interfacce in Java, per non parlare dell'override predefinito ... perché certo, ogni componente progettato per un'auto stradale dovrebbe essere in grado di essere sbattuto contro un'auto volante e funzionare - ehi sono entrambe auto ... Sono sicuro che la funzionalità predefinita di dire che il tuo navigatore satellitare non sarà influenzata dagli input predefiniti di beccheggio e rollio, perché le auto imbardano solo!

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.