Java - Kollision von Methodennamen bei der Implementierung der Schnittstelle

87

Wenn ich zwei Schnittstellen habe, die beide in ihren Zwecken sehr unterschiedlich sind, aber dieselbe Methodensignatur haben, wie kann ich eine Klasse dazu bringen, beide zu implementieren, ohne gezwungen zu sein, eine einzige Methode zu schreiben, die für beide Schnittstellen dient, und eine verschlungene Logik in die Methode zu schreiben Implementierung, die prüft, für welchen Objekttyp der Aufruf ausgeführt wird, und den richtigen Code aufruft?

In C # wird dies durch die sogenannte explizite Schnittstellenimplementierung überwunden. Gibt es einen gleichwertigen Weg in Java?

Bhaskar
quelle
36
Wenn eine Klasse zwei Methoden mit derselben Signatur implementieren muss, die unterschiedliche Aufgaben ausführen , führt Ihre Klasse mit ziemlicher Sicherheit zu viele Aufgaben aus.
Joachim Sauer
14
Dies gilt möglicherweise nicht immer IMO. Manchmal benötigen Sie in einer einzelnen Klasse Methoden, die einen externen Vertrag bestätigen müssen (wodurch die Signaturen eingeschränkt werden), die jedoch unterschiedliche Implementierungen aufweisen. Tatsächlich sind dies allgemeine Anforderungen beim Entwerfen einer nicht trivialen Klasse. Überladen und Überschreiben sind notwendigerweise Mechanismen, um Methoden zuzulassen, die verschiedene Dinge tun, die sich in der Signatur möglicherweise nicht oder nur geringfügig unterscheiden. Was ich hier habe, ist nur ein bisschen restriktiver darin, dass es keine Unterklassen zulässt / und nicht einmal zulässt geringste Abweichung bei den Unterschriften.
Bhaskar
1
Ich wäre gespannt, was diese Klassen und Methoden sind.
Uri
2
Ich bin auf einen solchen Fall gestoßen, in dem eine ältere "Address" -Klasse Personen- und Firmenschnittstellen implementiert hat, die eine getName () -Methode hatten, die einfach einen String aus dem Datenmodell zurückgibt. In einer neuen Geschäftsanforderung wurde angegeben, dass Person.getName () eine Zeichenfolge zurückgibt, die als "Nachname, Vorname" formatiert ist. Nach langen Diskussionen wurden die Daten stattdessen in der Datenbank neu formatiert.
Belwood
12
Nur zu behaupten, dass die Klasse mit ziemlicher Sicherheit zu viele Dinge tut, ist NICHT KONSTRUKTIV. Ich habe gerade diesen Fall, in dem meine Klasse Kollisionen mit mehod-Namen von zwei verschiedenen Schnittstellen hat und meine Klasse NICHT zu viele Dinge tut. Die Zwecke sind ziemlich ähnlich, machen aber etwas andere Dinge. Versuchen Sie nicht, eine offensichtlich schwerbehinderte Programmiersprache zu verteidigen, indem Sie den Fragesteller beschuldigen, schlechtes Software-Design implementiert zu haben!
J00hi

Antworten:

75

Nein, es gibt keine Möglichkeit, dieselbe Methode in einer Klasse in Java auf zwei verschiedene Arten zu implementieren.

Das kann zu vielen verwirrenden Situationen führen, weshalb Java dies nicht zugelassen hat.

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

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

Sie können eine Klasse aus zwei Klassen zusammenstellen, die jeweils eine andere Schnittstelle implementieren. Dann hat diese eine Klasse das Verhalten beider Schnittstellen.

class CompositeClass {
    ISomething class1;
    ISomething2 class2;
    void doSomething1(){class1.doSomething();}
    void doSomething2(){class2.doSomething();}
}
jjnguy
quelle
9
Aber auf diese Weise kann ich eine Instanz von CompositeClass nicht irgendwo übergeben, wo eine Referenz der Schnittstellen (ISomething oder ISomething2) erwartet wird. Ich kann nicht einmal erwarten, dass Client-Code meine Instanz in die entsprechende Schnittstelle umwandeln kann. Verliere ich also nicht etwas durch diese Einschränkung? Beachten Sie auch, dass wir auf diese Weise beim Schreiben von Klassen, die die jeweiligen Schnittstellen tatsächlich implementieren, den Vorteil verlieren, den Code in eine einzelne Klasse zu unterteilen, was manchmal ein ernstes Hindernis sein kann.
Bhaskar
9
@ Bhaskar, Sie machen gültige Punkte. Der beste Rat , den ich habe , ist ein hinzufügen ISomething1 CompositeClass.asInterface1();und ISomething2 CompositeClass.asInterface2();Verfahren zu dieser Klasse. Dann können Sie einfach das eine oder andere aus der zusammengesetzten Klasse herausholen. Es gibt jedoch keine gute Lösung für dieses Problem.
jjnguy
1
Können Sie ein Beispiel geben, wenn Sie von den verwirrenden Situationen sprechen, zu denen dies führen kann? Können wir uns den dem Methodennamen hinzugefügten Schnittstellennamen nicht als zusätzliche Bereichsauflösung vorstellen, die dann die Kollision / Verwirrung vermeiden kann?
Bhaskar
@ Bhaskar Es ist besser, wenn unsere Klassen dem Prinzip der Einzelverantwortung folgen. Wenn es eine Klasse gibt, die zwei sehr unterschiedliche Schnittstellen implementiert, sollte das Design meiner Meinung nach überarbeitet werden, um die Klassen aufzuteilen und die Verantwortung zu übernehmen.
Anirudhan J
1
Wie verwirrend wäre es wirklich, etwas zuzulassen wie public long getCountAsLong() implements interface2.getCount {...}[falls die Schnittstelle eine erfordert, longaber Benutzer der Klasse erwarten int] oder private void AddStub(T newObj) implements coolectionInterface.Add[vorausgesetzt, es collectionInterfacegibt eine canAdd()Methode und für alle Instanzen dieser Klasse wird sie zurückgegeben false]?
Supercat
13

Es gibt keinen wirklichen Weg, dies in Java zu lösen. Sie können innere Klassen als Problemumgehung verwenden:

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

Obwohl es keine Casts von AlfaBetabis zulässt Beta, sind Downcasts im Allgemeinen böse, und wenn zu erwarten ist, dass eine AlfaInstanz häufig auch einen BetaAspekt hat, möchten Sie aus irgendeinem Grund (normalerweise ist Optimierung der einzig gültige Grund) in der Lage sein zu konvertieren es Beta, könnten Sie eine Unter Schnittstelle von machen Alfamit Beta asBeta()drin.

gustafc
quelle
Meinst du eher anonyme Klasse als innere Klasse?
Zaid Masud
2
@ZaidMasud Ich meine innere Klassen, da sie auf den privaten Zustand des umschließenden Objekts zugreifen können. Diese inneren Klassen können natürlich auch anonym sein.
Gustafc
11

Wenn Sie auf dieses Problem stoßen, liegt dies höchstwahrscheinlich daran, dass Sie die Vererbung dort verwenden, wo Sie die Delegierung verwenden sollten . Wenn Sie zwei verschiedene, wenn auch ähnliche Schnittstellen für dasselbe zugrunde liegende Datenmodell bereitstellen müssen , sollten Sie eine Ansicht verwenden , um über eine andere Schnittstelle kostengünstig Zugriff auf die Daten zu erhalten.

Um ein konkretes Beispiel für den letzteren Fall zu geben, nehmen wir an, Sie möchten beide Collectionund implementieren MyCollection(die nicht von Collectioneiner inkompatiblen Schnittstelle erben und diese haben). Sie könnten ein Collection getCollectionView()und MyCollection getMyCollectionView()Funktionen bereitstellen, die eine leichte Implementierung von Collectionund ermöglichenMyCollection unter Verwendung derselben zugrunde liegenden Daten ermöglichen.

Für den ersteren Fall ... nehmen wir an, Sie möchten wirklich ein Array von Ganzzahlen und ein Array von Zeichenfolgen. Anstatt von beiden List<Integer>und zu erben List<String>, sollten Sie ein Mitglied vom Typ List<Integer>und ein anderes Mitglied vom Typ List<String>haben und auf diese Mitglieder verweisen, anstatt zu versuchen, von beiden zu erben. Selbst wenn Sie nur eine Liste von Ganzzahlen benötigen, ist es in diesem Fall besser, Komposition / Delegierung anstelle von Vererbung zu verwenden.

Michael Aaron Safyan
quelle
Das glaube ich nicht. Sie vergessen Bibliotheken, bei denen Sie verschiedene Schnittstellen implementieren müssen, um mit ihnen kompatibel zu sein. Sie können darauf stoßen, indem Sie mehrere widersprüchliche Bibliotheken viel häufiger verwenden, als Sie dies in Ihrem eigenen Code tun.
Nachtpool
1
@nightpool Wenn Sie mehrere Bibliotheken verwenden, für die jeweils unterschiedliche Schnittstellen erforderlich sind, muss ein einzelnes Objekt immer noch nicht beide Schnittstellen implementieren. Sie können das Objekt über Accessoren verfügen, um jede der beiden verschiedenen Schnittstellen zurückzugeben (und den entsprechenden Accessor aufrufen, wenn Sie das Objekt an eine der zugrunde liegenden Bibliotheken weitergeben).
Michael Aaron Safyan
1

Das "klassische" Java-Problem betrifft auch meine Android-Entwicklung ...
Der Grund scheint einfach zu sein:
Mehr Frameworks / Bibliotheken, die Sie verwenden müssen, leichter können Dinge außer Kontrolle geraten ...

In meinem Fall habe ich eine BootStrapperApp- Klasse von android.app.Application geerbt ,
während dieselbe Klasse auch eine Plattformschnittstelle eines MVVM-Frameworks implementieren sollte , um integriert zu werden.
Eine Methodenkollision trat bei einer getString () -Methode auf, die von beiden Schnittstellen angekündigt wird und in verschiedenen Kontexten eine unterschiedliche Implementierung aufweisen sollte.
Die Problemumgehung (hässlich ... IMO) verwendet eine innere Klasse, um alle Plattformen zu implementierenMethoden, nur aufgrund eines geringfügigen Methodensignaturkonflikts ... In einigen Fällen wird eine solche geliehene Methode überhaupt nicht verwendet (hat jedoch die Hauptentwurfs-Semantik beeinflusst).
Ich stimme eher zu, dass eine explizite Kontext- / Namespace-Angabe im C # -Stil hilfreich ist.

tiancheng
quelle
1
Ich habe nie bemerkt, wie nachdenklich und funktionsreich C # ist, bis ich anfing, Java für die Android-Entwicklung zu verwenden. Ich habe diese C # -Funktionen als selbstverständlich angesehen. Java fehlen zu viele Funktionen.
Verdammtes Gemüse
1

Die einzige Lösung, die mir in den Sinn kam, ist die Verwendung von Referenzobjekten für das Objekt, für das Sie mehrere Schnittstellen implementieren möchten.

Beispiel: Angenommen, Sie müssen 2 Schnittstellen implementieren

public interface Framework1Interface {

    void method(Object o);
}

und

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

Sie können sie in zwei Facador-Objekte einschließen:

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

und

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

Am Ende sollte die Klasse, die Sie wollten, so etwas wie

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

Sie können jetzt die getAs * -Methoden verwenden, um Ihre Klasse "verfügbar zu machen"

überhaupt nicht
quelle
0

Sie können ein Adaptermuster verwenden, damit diese funktionieren. Erstellen Sie zwei Adapter für jede Schnittstelle und verwenden Sie diese. Es sollte das Problem lösen.

AlexCon
quelle
-1

Alles schön und gut, wenn Sie die vollständige Kontrolle über den gesamten fraglichen Code haben und dies im Voraus implementieren können. Stellen Sie sich nun vor, Sie haben eine vorhandene öffentliche Klasse, die an vielen Stellen mit einer Methode verwendet wird

public class MyClass{

    private String name;

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

    public String getName(){
        return name;
    }
}

Jetzt müssen Sie es an den Standard-WizzBangProcessor übergeben, für den Klassen erforderlich sind, um das WBPInterface zu implementieren ... das ebenfalls eine getName () -Methode enthält. Anstelle Ihrer konkreten Implementierung erwartet diese Schnittstelle jedoch, dass die Methode den Namen eines Typs zurückgibt von Wizz Bang Processing.

In C # wäre es ein 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 müssen Sie jeden Punkt in der vorhandenen bereitgestellten Codebasis identifizieren, an dem Sie von einer Schnittstelle zur anderen konvertieren müssen. Sicher, die Firma WizzBangProcessor hätte getWizzBangProcessName () verwenden sollen, aber sie sind auch Entwickler. In ihrem Kontext war getName in Ordnung. Außerhalb von Java unterstützen dies die meisten anderen OO-basierten Sprachen. Java erzwingt selten, dass alle Schnittstellen mit derselben Methode NAME implementiert werden.

Die meisten anderen Sprachen haben einen Compiler, der gerne eine Anweisung entgegennimmt, um zu sagen: "Diese Methode in dieser Klasse, die mit der Signatur dieser Methode in dieser implementierten Schnittstelle übereinstimmt, ist ihre Implementierung." Schließlich geht es beim Definieren von Schnittstellen darum, dass die Definition von der Implementierung abstrahiert wird. (Lassen Sie mich nicht einmal anfangen, Standardmethoden in Interfaces in Java zu verwenden, geschweige denn Standardmethoden zu überschreiben ... denn sicher sollte jede für ein Straßenauto entwickelte Komponente in der Lage sein, in ein fliegendes Auto eingeschlagen zu werden und einfach zu arbeiten - hey Sie sind beide Autos ... Ich bin sicher, dass die Standardfunktionalität Ihres Navigationsgeräts nicht durch Standard-Pitch- und Roll-Eingaben beeinflusst wird, da Autos nur gieren!

user8232920
quelle