Implementieren mehrerer generischer Schnittstellen in Java

10

Ich benötige eine Schnittstelle, die sicherstellt, dass eine bestimmte Methode, einschließlich einer bestimmten Signatur, verfügbar ist. Bisher habe ich Folgendes:

public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

Das Problem tritt auf, wenn eine Klasse mehreren anderen Entitäten zugeordnet werden kann. Der Idealfall wäre dies (nicht Java):

public class Something implements Mappable<A>, Mappable<B> {
    public A mapTo(A someObject) {...}
    public B mapTo(B someOtherObject) {...}
}

Was wäre der beste Weg, um dies so "generisch" wie möglich zu erreichen?

estani
quelle

Antworten:

10

Dies können Sie aufgrund von Type Erasure natürlich nicht tun . Zur Laufzeit haben Sie zwei Methoden public Object mapTo(Object), die offensichtlich nicht koexistieren können.

Leider versuchen Sie einfach, über das Typensystem von Java hinauszugehen.

Angenommen, Ihr generischer Typ ist immer ein erstklassiger Typ und nicht selbst generisch, könnten Sie mit der Methode ein ähnliches nach außen gerichtetes Verhalten erzielen mapTo(Object, Class), mit dem Sie die Laufzeit der angegebenen Klasse überprüfen und entscheiden können, welches Verhalten verwendet werden soll. Natürlich ist dies ziemlich unelegant - und erfordert ein manuelles Casting des Rückgabewerts -, aber ich denke, es ist das Beste, was Sie tun können. Wenn Ihre generischen Typen selbst generisch sind, werden auch ihre generischen Parameter gelöscht und ihre Klassen sind gleich, sodass diese Methode nicht funktioniert.

Ich möchte jedoch auch auf die Antwort von @ Joachim hinweisen. Dies kann ein Fall sein, in dem Sie das Verhalten in getrennte Komponenten aufteilen und das gesamte Problem umgehen können.

Phoshi
quelle
3

Wie Sie gesehen haben, können Sie dieselbe Schnittstelle nicht zweimal mit unterschiedlichen Typparametern implementieren (aufgrund der Löschung: Zur Laufzeit handelt es sich um dieselben Schnittstellen).

Außerdem verstößt dieser Ansatz gegen das Prinzip der Einzelverantwortung: Ihre Klasse sollte sich darauf konzentrieren, ein Something(was auch immer das bedeutet) zu sein, und sollte die Zuordnung zu Aoder B zusätzlich zu dieser Aufgabe nicht durchführen.

Es klingt so, als ob Sie wirklich ein Mapper<Something,A>und ein haben sollten Mapper<Something,B>. Auf diese Weise hat jede Klasse eine klar definierte Verantwortung, und Sie haben nicht das Problem, dieselbe Schnittstelle zweimal zu implementieren.

Joachim Sauer
quelle
Nun, die Idee ist, die Klasse dafür verantwortlich zu machen, ihren Inhalt in andere Objekte umzuwandeln. Dann gibt es einen Dispatcher, der sie klassenunabhängig behandelt, daher die Generika-Anforderung. Ich werde ein wenig über das Extrahieren der Logik nachdenken, obwohl dies tatsächlich bedeutet, dass ich die Klasse in zwei Teile aufteile, aber sie bleiben eng miteinander verbunden (der Zugriff auf das Feld sollte gewährt werden, wobei das Ändern eines Feldes meistens das Ändern des anderen usw. impliziert).
Estani
@estani: Ja, sie sind etwas eng miteinander verbunden, aber sie haben unterschiedliche Verantwortlichkeiten. Denken Sie auch darüber nach: Wenn Sie eine neue Klasse einführen Cund Somethingdarauf abgebildet werden möchten , müssen Sie Änderungen vornehmen Something, was zu viel Kopplung bedeutet. Nur ein neues hinzuzufügen SoemthingToCMapperist weniger aufdringlich.
Joachim Sauer
1
+1 dazu - im Allgemeinen sollten Sie die Komposition der Vererbung vorziehen, wenn Sie versuchen, das zu erreichen, was das OP will (in Java). Java 8 mit Standardmethoden macht dies noch einfacher - aber noch kann nicht jeder auf den neuesten Stand kommen :-).
Martijn Verburg
0

Da dies nicht erlaubt ist, mehrere Schnittstellen zu implementieren, können Sie die Verwendung der Kapselung in Betracht ziehen. (Beispiel mit Java8 +)

// Mappable.java
public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

// TwoMappables.java
public interface TwoMappables {
    default Mappable<A> mapableA() {
         return new MappableA();
    }

    default Mappable<B> mapableB() {
         return new MappableB();
    }

    class MappableA implements Mappable<A> {}
    class MappableB implements Mappable<B> {}
}

// Something.java
public class Something implements TwoMappables {
    // ... business logic ...
    mapableA().mapTo(A);
    mapableB().mapTo(B);
}

Weitere Informationen und Beispiele finden Sie hier: Wie erstelle ich eine Java-Klasse, die eine Schnittstelle mit zwei generischen Typen implementiert? .

Winter
quelle
-1
public interface IMappable<S, T> {
    T MapFrom(S source);
}

// T - target
// S - source

Wenn Sie User UserDTO und User UserViewModel zuordnen möchten, benötigen Sie zwei separate Implementierungen. Stapeln Sie all diese Logik nicht in einer einzigen Klasse - das macht keinen Sinn.

Update, um Joachim glücklich zu machen

public interface ITypeConverter<TSource, TDestination>
{
    TDestination Convert(TSource source);
}

Aber jetzt befinden wir uns im Bereich Automapper ( http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters ).

CodeART
quelle
Ich denke nicht, dass IMappablees ein guter Name für etwas ist, das andere Dinge abbildet. Mapper(oder IMapper, wenn Sie müssen ;-)) ist wahrscheinlich korrekter. (Übrigens: Nein, das war nicht meine Ablehnung).
Joachim Sauer
Ich habe das, was in der Frage war, genommen und ein I vorangestellt, um die Tatsache hervorzuheben, dass es sich um eine Schnittstelle handelt. Ich löse das Designproblem gemäß der Frage im Gegensatz zu einem Namensproblem.
CodeART
1
Entschuldigung, aber meiner Meinung nach kann man ein Designproblem nicht wirklich "lösen" und die Benennung ignorieren. Design bedeutet verständliche Strukturen. Falsche Namen sind ein Problem für das Verständnis.
Joachim Sauer
Update sollte Ihre Gedanken
beruhigen ;-)
1
@CodeART Wenn ich Ihre Antwort richtig verstanden habe, impliziert dies "MapFrom" (das in Kleinbuchstaben geschrieben werden sollte ;-) erstellt das Objekt. In meinem Fall werden nur Informationen zu einem bereits erstellten Objekt ausgefüllt.
Estani