Fluent-API mit Vererbung und generics
Schreibe ich eine fluent-API zu konfigurieren und instanziieren eine Reihe von "message" Objekte. Ich habe eine Hierarchie von Nachrichtentypen.
Zugriff auf die Methode von Subklassen, wenn mithilfe der fluent-API, die ich verwendet, Generika einstellen der Unterklassen und stellen Sie alle fließend Methoden (beginnen mit "mit") zurück, die den generischen Typ. Beachten Sie, dass ich ausgelassen, die meisten des Körpers, der fließend Methode; eine Menge von Konfiguration geht auf in Ihnen.
public abstract class Message<T extends Message<T>> {
protected Message() {
}
public T withID(String id) {
return (T) this;
}
}
Die konkrete Unterklassen redefinieren der generische Typ ähnlich.
public class CommandMessage<T extends CommandMessage<T>> extends Message<CommandMessage<T>> {
protected CommandMessage() {
super();
}
public static CommandMessage newMessage() {
return new CommandMessage();
}
public T withCommand(String command) {
return (T) this;
}
}
public class CommandWithParamsMessage extends
CommandMessage<CommandWithParamsMessage> {
public static CommandWithParamsMessage newMessage() {
return new CommandWithParamsMessage();
}
public CommandWithParamsMessage withParameter(String paramName,
String paramValue) {
contents.put(paramName, paramValue);
return this;
}
}
Dieser code funktioniert, d.h. ich kann instanziieren alle Klassen und alle fließend Methoden:
CommandWithParamsMessage msg = CommandWithParamsMessage.newMessage()
.withID("do")
.withCommand("doAction")
.withParameter("arg", "value");
Aufruf des fluent-Methoden in beliebiger Reihenfolge, ist ein wichtiges Ziel der hier.
Jedoch der compiler warnt, dass alle return (T) this
unsicher sind.
Type safety: Unchecked cast from Nachricht an T
Ich bin mir nicht sicher, wie ich könnte die Reorganisation der Hierarchie, damit dieser code wirklich sicher. Obwohl es funktioniert, ist der Einsatz von Generika in dieser Mode fühlt sich wirklich verworren.
Vor allem, bin ich nicht in der Lage vorauszusehen, Situationen, in denen Laufzeit-Ausnahmen wird passieren, wenn ich einfach die Warnungen ignorieren.
Es wird neue message-Typen, so muss ich den code erweiterbar.
Wenn die Lösung zu vermeiden, die erbschaft insgesamt möchte ich auch erhalten, Vorschlag von alternativen.
Gibt es andere Fragen hier SO, dass die Adresse einem ähnlichen Problem. Sie zeigen eine Lösung, bei der alle zwischen Klassen sind Abstrakt, und deklarieren Sie eine Methode, wie protected abstract self()
. Dennoch, am Ende ist es sicher nicht.
- Der code ist nicht sicher, obwohl, ist es? Woher wissen Sie, dass
Message
ist der TypT
? - da
T extends Message<T>
. - Beachten Sie aber, Gefahren der Korrelation von Subtyp-Polymorphismus generische Polymorphie
- Das nicht nur bedeuten, dass der parameter erweitert Botschaft? Die Nachricht, die verwendet wird ist nicht unbedingt der Typ T?
- die Nachricht, die verwendet wird (werden), ist vom Typ T, weil die Unterklassen definieren Sie T als eine eigene Art, wenn die Erweiterung Message<T extends Message>
- aber die abstrakte API nicht wissen, dass immer für wahr halten
- Versuchen Sie einfach, mit
public Message<T> withID(String id) {return this;}
stattpublic T withID(String id) {return (T) this;}
. Tun Sie dasselbe fürCommandMessage
als gut. - Ich versuchte, dass jetzt die withCommand gibt den falschen Typ und ich bekomme eine Fehlermeldung:
The method withCommand(String) is undefined for the type Message<CommandMessage<CommandWithParamsMessage>>
- Ich bin nicht davon überzeugt, dass eine Vererbungshierarchie ist der beste Ansatz, um Ihren Fall.
- Eigentlich denke ich es ist. Im wesentlichen, was OP braucht, ist für die superclass-Methoden zum zurückgeben von der Klasse des aktuellen Objekts, ruft Sie, und durch das design von Java, wir sind beschränkt auf Generika. Beachten Sie, dass diese Frage ähnlich wie die Frage: stackoverflow.com/questions/4031857/...
- Siehe auch stackoverflow.com/q/7354740/685806
Du musst angemeldet sein, um einen Kommentar abzugeben.
Dein code ist grundsätzlich eine unsichere Verwendung von Generika. Zum Beispiel, wenn ich Schreibe, eine neue Klasse, die erweitert die Nachricht, sagen Bedrohlich, und hat eine neue Methode doSomething(), und dann erstelle ich eine Meldung parametriert, die von dieser neuen Klasse, und es erstellt eine Instanz der Nachricht und versucht dann, warf es zu seiner Unterklasse. Da es sich jedoch um eine Instanz der Nachricht, und nicht der Gefahr, ein Versuch zum aufrufen dieser Meldung zu einer Ausnahme führen. Da es nicht möglich ist, für die Nachricht von doSOmething().
Weiter, es ist auch unnötig zu verwenden, Generika hier. Plain old Vererbung funktioniert. Da die sub-Typen kann Methoden überschreiben, indem Sie Ihre Rückgabetypen mehr bestimmte, die man haben kann:
Dann
Diese funktionieren, auf dem Verständnis, dass Sie Ihre Argumente in der richtigen Reihenfolge:
wird nicht funktionieren, aber
Erfolgreich sein wird, da es nur "up-Typen", schließlich der Rückkehr eine "message" - Klasse. Wenn Sie wollen, dass es nicht zu "uptype", dann überschreiben Sie einfach die geerbte Befehle, und jetzt können Sie die Methoden aufrufen, die in beliebiger Reihenfolge, da Sie alle wieder die ursprüngliche Art.
E. g.
Nun werden Sie fließend Rückkehr eine CommandWithParamsMessage mit den beiden fließend Anrufe vor.
Tut dies Ihr problem lösen, oder habe ich das falsch verstanden, deine Absicht?
Habe ich etwas getan, wie dies vor. Es kann hässlich werden. In der Tat, ich habe versucht es weitere Male, als ich ihn einmal verwendet habe; in der Regel wird es gelöscht und ich versuche zu finden, eine bessere design. Das heißt, um Ihnen dabei zu helfen ein wenig weiter die Straße hinunter, versuchen Sie dies:
Haben Ihre abstrakte Klassen deklarieren Sie eine Methode wie:
Diese ersetzen kann
this
in Ihrem return-Anweisungen. Die Unterklassen werden benötigt, um etwas von dem zurückgeben, die matches der bound forT
- aber es gibt auch keine Garantie, dass Sie wieder das gleiche Objekt.Wenn Sie ändern Sie die Signaturen wie diese sollten Sie weder Warnungen, noch benötigen Sie irgendwelche casts:
Der compiler warnt Sie dieses unsichere operation, weil es nicht sachlich überprüfen Sie die Richtigkeit des Codes. Dies macht es, als eine Angelegenheit von der Tat, unsicher und es gibt nichts Sie tun können, um zu verhindern, dass diese Warnung. Obwohl eine unsichere operation ist nicht kompiliert, überprüft werden, kann es noch legitim sein, zur Laufzeit. Wenn Sie die Umgehung der compiler überprüfen, es ist jedoch Ihre Aufgabe, Sie zu überprüfen Sie Ihre eigenen code für Ihre Verwendung der richtigen Typen, die, was die
@SupressWarning("unchecked")
annotation ist für.Anwenden zu deinem Beispiel:
ist in Ordnung, denn man kann tatsächlich sagen, mit Sicherheit, dass dies
Message
Instanz ist immer von dem Typ, dargestellt durchT
. Aber der Java-compiler (noch) nicht. Als mit anderen, Unterdrückung von Warnungen, die Schlüssel zu verwenden, die Anmerkung ist zu minimieren, Ihren Anwendungsbereich! Andernfalls können Sie leicht behalten die annotation der Unterdrückung durch Zufall nach code-änderungen, die dazu führen, dass Ihre ehemaligen manuelle überprüfung auf Typsicherheit als ungültig.Als Sie nur zurückgeben
this
Beispiel, Sie können jedoch leicht die Auslagerung der Aufgabe an eine bestimmte Methoden, wie empfohlen, in einer weiteren Antwort. Definieren Sie eineprotected
Methode wieund Sie können rufen Sie immer den mutator wie hier:
Als eine weitere option, und wenn es möglich ist, für Sie zu implementieren, betrachten einen unveränderlichen builder, die nur macht seine API-Schnittstellen, sondern implementiert einen kompletten generator. Dies ist, wie ich normalerweise bauen die fluent interfaces in diesen Tagen:
Können Sie, natürlich, erstellen Sie intelligentere Konstruktionen, wo Sie sich, vermeiden Sie die nicht verwendeten Felder. Wenn Sie wollen, schauen Sie sich Beispiele von mir mit diesem Ansatz, der Blick auf meine zwei Projekte, die fluent-interfaces sehr stark:
Ist dies keine Lösung für das ursprüngliche problem. Es ist nur ein Versuch, zu erfassen, Ihre tatsächliche Absicht, und skizzieren einen Ansatz, wo, wo das ursprüngliche problem nicht angezeigt. (Ich mag die Generika - aber Klasse Namen wie
CommandMessage<T extends CommandMessage<T>> extends Message<CommandMessage<T>>
machen mich schaudern...)Weiß ich, dass dies strukturell eher anders aus, was Sie ursprünglich gefragt, und Sie haben vielleicht einige details weggelassen bei der Frage, verengen das Spektrum der möglich Antworten, so dass die folgende ist nicht mehr anwendbar.
Aber wenn ich Verstand Ihre Absicht richtig, Sie könnte in Erwägung ziehen, lassen die Subtypen behandelt werden, indem der fließend Anrufe.
Die Idee hier ist, dass Sie zunächst nur erstellen Sie eine einfache
Message
:Auf diese Meldung, können Sie die
withID
Methode - das ist die einzige Methode, die allen Nachrichten gemein haben. DiewithID
Methode gibt eineMessage
in diesem Fall.Bis jetzt, die Nachricht ist weder ein
CommandMessage
noch eine andere spezielle form. Allerdings, wenn Sie rufen Sie diewithCommand
Methode, die Sie offensichtlich wollen, zu konstruieren, einCommandMessage
- so dass Sie jetzt einfach wieder einCommandMessage
:Ebenso, wenn Sie rufen Sie die
withParameter
Methode, erhalten Sie einCommandWithParamsMessage
:Diese Idee ist in etwa (!) inspiriert von einem blog-Eintrag, die ist in Deutsch, aber der code, der schön zeigt, wie dieses Konzept kann verwendet werden, zu konstruieren, type-safe "Select-From-Where" Abfragen.
Hier der Ansatz wird skizziert, grob abgestimmt für Ihren Anwendungsfall. Natürlich gibt es einige details, in denen die Umsetzung wird davon abhängen, wie diese tatsächlich genutzt werden - aber ich hoffe, dass die Idee klar wird.
CommandMessage<T extends CommandMessage<T>> extends Message<CommandMessage<T>>
). Es werden wahrscheinlich weitere Antworten. Die Antwort von WilliamPrice ist schon ein Schritt, manchmal bekannt als "getThis" trick. Auch haben Sie einen Blick auf angelikalanger.com/GenericsFAQ/FAQSections/... - einige wertvolle Informationen auf dieser Seite, vielleicht hilftself()
. Am Ende ist es sicher nicht. Ich muss immer noch verdauen diese und sehen, wie kann ich mein API.