Java Generics - wie man ein Gleichgewicht zwischen Ausdruckskraft und Einfachheit findet

8

Ich entwickle Code, der Generika verwendet, und eines meiner Leitprinzipien war es, ihn für zukünftige Szenarien und nicht nur für heutige Szenarien nutzbar zu machen. Mehrere Mitarbeiter haben jedoch zum Ausdruck gebracht, dass ich die Lesbarkeit möglicherweise aus Gründen der Erweiterbarkeit abgewogen habe. Ich wollte Feedback zu möglichen Lösungsmöglichkeiten einholen.

Um genau zu sein, hier ist eine Schnittstelle, die eine Transformation definiert: Sie beginnen mit einer Quellensammlung von Elementen und wenden die Transformation auf jedes Element an, wobei die Ergebnisse in einer Zielsammlung gespeichert werden. Außerdem möchte ich in der Lage sein, die Zielsammlung an den Anrufer zurückzugeben, und anstatt sie zu zwingen, eine Sammlungsreferenz zu verwenden, möchte ich, dass sie den Sammlungstyp verwenden können, den sie tatsächlich für die Zielsammlung bereitgestellt haben.

Schließlich mache ich es möglich, dass sich die Art der Elemente in der Zielsammlung von der Art der Elemente in der Quellensammlung unterscheidet, da dies möglicherweise die Transformation bewirkt. In meinem Code bilden beispielsweise mehrere Quellelemente nach der Transformation ein Zielelement.

Dies ergibt die folgende Schnittstelle:

interface Transform<Src, Dst> {
    <DstColl extends Collection<? super Dst>> DstColl transform(
                Collection<? extends Src> sourceCollection,
                DstColl                   destinationCollection);
}

Ich habe versucht, alle nett zu sein und das PECS-Prinzip von Josh Bloch (Produzent erweitert, Verbraucher super) anzuwenden, um sicherzustellen, dass die Schnittstelle gegebenenfalls mit Super- und Subtypen verwendet werden kann. Das Endergebnis ist eine Art Monstrosität.

Nun wäre es schön gewesen, wenn ich diese Schnittstelle erweitern und irgendwie spezialisieren könnte. Wenn es mir zum Beispiel nicht wirklich wichtig ist, mit Untertypen der Quellelemente und Supertypen der Zielelemente gut zu spielen, könnte ich Folgendes haben:

interface SimpleTransform<Src, Dst> {
    <DstColl extends Collection<Dst>> DstColl transform(
                  Collection<Src> sourceCollection,
                  DstColl         destinationCollection);
}

In Java gibt es dafür keine Möglichkeit. Ich möchte Implementierungen dieser Schnittstelle zu etwas machen, das andere tatsächlich in Betracht ziehen würden, anstatt in Angst zu laufen. Ich habe mehrere Optionen in Betracht gezogen:

  • Geben Sie die Zielsammlung nicht zurück. Scheint seltsam, wenn man bedenkt, dass man eine Transformation durchführt, aber nichts zurückbekommt.
  • Haben Sie eine abstrakte Klasse, die diese Schnittstelle implementiert, aber dann die Parameter in etwas einfacher zu übersetzendes übersetzt und eine andere Methode "translateImpl ()" aufruft, die die einfachere Signatur hat und somit für Implementierer eine geringere kognitive Belastung darstellt. Aber dann ist es seltsam, eine abstrakte Klasse schreiben zu müssen, um eine Benutzeroberfläche benutzerfreundlich zu gestalten.
  • Verzichten Sie auf Erweiterbarkeit und haben Sie einfach die einfachere Oberfläche. Koppeln Sie dies möglicherweise damit, dass die Zielsammlung nicht zurückgegeben wird. Aber das schränkt meine Möglichkeiten in der Zukunft ein.

Was denken Sie? Fehlt mir ein Ansatz, den ich verwenden könnte?

RuslanD
quelle
2
Es ist keine gute Idee, ein zukünftiges Szenario vorherzusagen, anstatt den Anwendungsfall zu kennen. Hier kommt das KISS- Prinzip ins Spiel.
Lorus
1
Zusätzlich zum KISS-Prinzip gibt es auch das YAGNI- Prinzip, das vorschlägt, dass Sie das nicht tun sollten.
Frank
@lorus - Meine Idee war es, eine "Transformations" -Schnittstelle zu haben, die ich für eine Vielzahl von Transformationen verwenden kann, im Gegensatz zu einem bestimmten Fall, der auf eine bestimmte Kombination von Quell- und Zielelementtypen zugeschnitten ist. Eine Möglichkeit, meinen Beitrag zusammenzufassen, lautet: "Wie halte ich ihn so einfach, dass in Zukunft keine wesentlichen Nacharbeiten erforderlich sind?" Wir befinden uns in einem typischen Unternehmensumfeld, in dem die Fristen König sind, und ich kann nicht einfach Dinge umgestalten, es sei denn, es gibt einen soliden Geschäftsgrund. Gleiches gilt für YAGNI in Franks Kommentar.
RuslanD
Stattdessen Collection<? extends Src> srcCollectionsollten SieIterable<? extends Src>
Kevin Cline

Antworten:

3

Guava Collections bietet diese Funktionalität bereits und wenn Sie etwas länger warten können, bietet Java 8 dies auch :-). FWIW Ich denke, Ihre Verwendung von ? extends Tist die richtige Redewendung. Sie können nicht viel tun, da Java Type Erasure verwendet

Martijn Verburg
quelle
Leider ist es unwahrscheinlich, dass ich mein Team und mein Management davon überzeugen kann, direkt von Java 6 auf Java 8 zu springen :) Könnten Sie in der Zwischenzeit bitte klarstellen, was Sie unter "Guava Collections bietet diese Funktionalität bereits" verstehen? Sprechen Sie über die Transformation von Sammlungen? Ein Zeiger auf eine relevante Ressource wäre sehr nützlich. Vielen Dank!
RuslanD
Ich bin verwirrt darüber, warum das Löschen hier von Bedeutung ist. Denken Sie darüber nach, welche generischen C # x.0-Parameter ein- und ausgehen? / Die Verwendung Collection<? super Dst>wäre hier ideal, wenn nicht auch derselbe Typ zurückgegeben würde (vermutlich dieselbe Referenz, was keine wirklich gute Idee ist (ich denke, Java SE 8 wird dies für einige Methoden tun ( into))). Es ist interessant zu bemerken, dass Leute wie Collections.fillWildcard unnötig verwenden, um ausdrucksvoller zu sein. / IIRC, Java SE 6 wird diesen Monat endlich nicht mehr unterstützt.
Tom Hawtin - Tackline
1
Guave ist code.google.com/p/guava-libraries
MatrixFrog
2
+1 für Guava - es ist ausgezeichnet und sollte Teil des Toolkits jedes Java-Entwicklers sein (auch Joda Time und Joda Money)
Gary Rowe
0

Ich persönlich sehe kein großes Problem mit der Art und Weise, wie Sie Ihre Benutzeroberfläche definiert haben. Ich bin es jedoch gewohnt, mit Generika zu spielen, und ich kann verstehen, dass Ihre Mitarbeiter Ihren Code als etwas mundvoll empfinden würden, wenn dies nicht der Fall ist.

Wenn ich Sie wäre, würde ich mich für die dritte Lösung entscheiden, die Sie angeben: Vereinfachen Sie die Dinge auf Kosten einer möglichen späteren Rückkehr. Denn vielleicht müssen Sie es am Ende doch nicht; oder Sie könnten es irgendwann vollständig nutzen, aber nicht genug, um den Aufwand auszugleichen, den Sie unternommen haben, um diese Benutzeroberfläche so allgemein zu gestalten, sowie den Aufwand, den Ihre Mitarbeiter möglicherweise unternommen haben, um ihre Köpfe herumzuwickeln es.

Eine andere zu berücksichtigende Sache ist, wie oft Sie diese Schnittstelle verwenden und auf welcher Komplexitätsstufe. Wie nützlich diese Schnittstelle in Ihren Projekten ist. Wenn es eine hohe Wiederverwendbarkeit einiger Komponenten ermöglicht (und nicht nur potenzielle, sondern auch tatsächliche), ist es eine gute Sache. Wenn in 90% der Fälle auch eine viel einfachere Benutzeroberfläche funktioniert, können Sie sich fragen, ob sich für die verbleibenden 10% eine komplexe Benutzeroberfläche als nützlich erweisen würde oder nicht.

Ich denke nicht, dass es eine sehr gute Idee wäre, sie nicht zu verwenden, superund extendwenn Sie im Moment darauf verzichten können, werden Ihre Kollegen sicher nichts dagegen haben, sie verschwinden zu sehen. Haben Sie jedoch wirklich so viele Situationen, in denen Sie auch den Typ ändern müssen Collection?

Einige bereits gegebene Ratschläge sind wirklich gut, und ich stimme Frank darin zu, dem YAGNI-Prinzip zu folgen, es sei denn, Sie haben sehr gute Gründe, dies nicht zu tun. Sie können den Code immer noch ändern, wenn mehr Komplexität erforderlich ist. Es ist jedoch nicht hilfreich, Dinge zu entwickeln, von denen Sie nicht sicher sind, ob sie bald verwendet werden. Auch Martijns Ratschlag zur Verwendung von Guave sollte ernsthaft in Betracht gezogen werden. Ich hatte noch keine Gelegenheit, es zu verwenden, aber ich habe viel Gutes darüber gehört, auch in einer Präsentation, in der das Transformer-Muster besprochen wurde (Sie können es online auf InfoQ ansehen, wenn Sie interessiert sind).

Wäre es in Ihrer aktuellen Benutzeroberfläche nicht besser destinationCollection, vom Typ zu sein Class<DstColl>?

KevinLH
quelle
0

Ich mag es, Java-Sammlungen zu verpacken. Die richtige Kapselung kann wirklich helfen. Dieses Muster muss auf der Grundlage des Typs verknüpft werden, lässt jedoch jede Codezeile den Typ der Sammlung nicht kennen.

Angenommen, es gibt eine Liste von Produkten. Die Produktklasse wäre unveränderlich und würde einige nützliche Methoden enthalten. Die Produktlistenklasse kann ein Produkt oder eine andere Liste hinzufügen. Wenn Sie eine Methode über die gesamte Liste ausführen möchten, befindet sie sich in der Produktlistenklasse. Es gibt eine Methode, die eine Filterlistenklasse verwendet und eine neue Produktliste mit den gefilterten erstellt. Das Gefilterte würde aus dem Original gelöscht.

Es gibt eine Filterschnittstelle, die entscheidet, ob EIN Produkt den Filter passiert. Es würde eine Filterlistenklasse geben, die eine Liste von Filtern enthält und die Schnittstelle implementiert, indem ALLE Filter ein Produkt übergeben müssen, damit das Produkt übergeben werden kann.

Lustige Sache ist, ich kann einen Stapel und ändern Sie es in eine verknüpfte Liste ohne merkliche Änderungen wie diese. Es kann Schleifen und Bedingungen sehr auffällig und einfach ausführen. So wird der zwingende Code verkleidet und die Flexibilität erhöht. Und ist gut orgonisiert.

Akash Patel
quelle
Ist Ihnen klar, dass Sie all das mit Standard-Java-Sammlungen, -Streams und -Lambdas in wenigen Codezeilen erledigen können? Eine spezielle "Produktlistenklasse" zu haben, wäre eine wirklich, wirklich schreckliche Zeitverschwendung, die nichts als Wartungsprobleme verursacht.
Michael Borgwardt