Geben Sie diesem Artikel von Dr. Dobbs und insbesondere dem Builder-Muster an, wie wir mit dem Fall der Unterklasse eines Builders umgehen. Eine naive Implementierung wäre eine gekürzte Version des Beispiels, in dem wir eine Unterklasse zum Hinzufügen einer GVO-Kennzeichnung erstellen möchten:
public class NutritionFacts {
private final int calories;
public static class Builder {
private int calories = 0;
public Builder() {}
public Builder calories(int val) { calories = val; return this; }
public NutritionFacts build() { return new NutritionFacts(this); }
}
protected NutritionFacts(Builder builder) {
calories = builder.calories;
}
}
Unterklasse:
public class GMOFacts extends NutritionFacts {
private final boolean hasGMO;
public static class Builder extends NutritionFacts.Builder {
private boolean hasGMO = false;
public Builder() {}
public Builder GMO(boolean val) { hasGMO = val; return this; }
public GMOFacts build() { return new GMOFacts(this); }
}
protected GMOFacts(Builder builder) {
super(builder);
hasGMO = builder.hasGMO;
}
}
Jetzt können wir Code wie folgt schreiben:
GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);
Aber wenn wir die Reihenfolge falsch verstehen, schlägt alles fehl:
GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);
Das Problem ist natürlich, dass NutritionFacts.Builder
a zurückgegeben wird NutritionFacts.Builder
, nicht a GMOFacts.Builder
. Wie lösen wir dieses Problem, oder gibt es ein besseres Muster?
Hinweis: Diese Antwort auf eine ähnliche Frage bietet die Klassen, die ich oben habe. Meine Frage bezieht sich auf das Problem, sicherzustellen, dass die Builder-Aufrufe in der richtigen Reihenfolge sind.
quelle
build()
die Ausgabe vonb.GMO(true).calories(100)
?Antworten:
Sie können es mit Generika lösen. Ich denke, dies nennt man die "seltsam wiederkehrenden generischen Muster".
Machen Sie den Rückgabetyp der Builder-Methoden der Basisklasse zu einem generischen Argument.
Instanziieren Sie nun den Basis-Builder mit dem abgeleiteten Klassen-Builder als generischem Argument.
quelle
implements
anstattextends
oder (c) alles wegwerfen. Ich habe jetzt einen seltsamen Kompilierungsfehler, woleafBuilder.leaf().leaf()
undleafBuilder.mid().leaf()
ist OK, aberleafBuilder.leaf().mid().leaf()
schlägt fehl ...return (T) this;
führt zu einerunchecked or unsafe operations
Warnung. Das ist unmöglich zu vermeiden, oder?unchecked cast
Warnung zu lösen , lesen Sie die untenBuilder<T extends Builder>
tatsächlich ein Rohtyp ist - dies sollte seinBuilder<T extends Builder<T>>
.Builder
fürGMOFacts
muss auch generisch seinBuilder<B extends Builder<B>> extends NutritionFacts.Builder<Builder>
- und dieses Muster kann so viele Ebenen wie erforderlich fortsetzen. Wenn Sie einen nicht generischen Builder deklarieren, können Sie das Muster nicht erweitern.Nur für die Aufzeichnung, um das loszuwerden
Für die
return (T) this;
Aussage, über die @dimadima und @Thomas N. sprechen, gilt in bestimmten Fällen die folgende Lösung.Erstellen Sie
abstract
den Builder, der den generischen Typ deklariert (T extends Builder
in diesem Fall), und deklarieren Sie dieprotected abstract T getThis()
abstrakte Methode wie folgt:Weitere Informationen finden Sie unter http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 .
quelle
build()
Methode die NutrutionFacts hier zurück?public GMOFacts build() { return new GMOFacts(this); }
BuilderC extends BuilderB
undBuilderB extends BuilderA
wenn diesBuilderB
nicht derabstract
Basierend auf einem Blog-Beitrag erfordert dieser Ansatz, dass alle Nicht-Blatt-Klassen abstrakt und alle Blatt-Klassen endgültig sind.
Dann haben Sie eine Zwischenklasse, die diese Klasse und ihren Builder erweitert, und so viele weitere, wie Sie benötigen:
Und schließlich eine konkrete Blattklasse, die alle Builder-Methoden für jeden ihrer Elternteile in beliebiger Reihenfolge aufrufen kann:
Anschließend können Sie die Methoden in beliebiger Reihenfolge aus einer der Klassen in der Hierarchie aufrufen:
quelle
B
. Es stellt sich immer als Basisklasse heraus.<T extends SomeClass, B extends SomeClass.Builder<T,B>> extends SomeClassParent.Builder<T,B>
Muster folgt wie die zwischengeschaltete SecondLevel-Klasse, sondern stattdessen bestimmte Typen deklariert. Sie können eine Klasse erst einrichten, wenn Sie mit den bestimmten Typen zum Blatt gelangen. Wenn Sie dies jedoch tun, können Sie sie nicht weiter erweitern, da Sie die bestimmten Typen verwenden und das Muster "Neugierig wiederkehrende Vorlagen" aufgegeben haben. Dieser Link könnte helfen: angelikalanger.com/GenericsFAQ/FAQSections/…Sie können auch die
calories()
Methode überschreiben und den erweiterten Builder zurückgeben lassen. Dies wird kompiliert, da Java kovariante Rückgabetypen unterstützt .quelle
Es gibt auch eine andere Möglichkeit, Klassen entsprechend zu erstellen
Builder
Muster , die "Komposition gegenüber Vererbung bevorzugen" entspricht.Definieren Sie eine Schnittstelle, die diese übergeordnete Klasse
Builder
erbt:Die Implementierung von
NutritionFacts
ist fast dieselbe (mit Ausnahme derBuilder
Implementierung der 'FactsBuilder'-Schnittstelle):Die
Builder
einer untergeordneten Klasse sollte dieselbe Schnittstelle erweitern (mit Ausnahme unterschiedlicher generischer Implementierungen):Beachten Sie, dass dies
NutritionFacts.Builder
ein Feld im Inneren istGMOFacts.Builder
(aufgerufenbaseBuilder
). Die von der gleichnamigen Methode derFactsBuilder
Schnittstellenaufrufe implementiertebaseBuilder
Methode:Es gibt auch eine große Änderung im Konstruktor von
GMOFacts(Builder builder)
. Der erste Aufruf des Konstruktors an den Konstruktor der übergeordneten Klasse sollte entsprechend übergeben werdenNutritionFacts.Builder
:Die vollständige Implementierung der
GMOFacts
Klasse:quelle
Ein vollständiges 3-Ebenen-Beispiel für die Vererbung mehrerer Builder würde folgendermaßen aussehen :
(Für die Version mit einem Kopierkonstruktor für den Builder siehe das zweite Beispiel unten)
Erste Ebene - Eltern (möglicherweise abstrakt)
Zweites Level
Drittes Level
Und ein Anwendungsbeispiel
Eine etwas längere Version mit einem Kopierkonstruktor für den Builder:
Erste Ebene - Eltern (möglicherweise abstrakt)
Zweites Level
Drittes Level
Und ein Anwendungsbeispiel
quelle
Wenn Sie nicht auf eine oder drei spitze Klammern blicken möchten oder Sie vielleicht nicht fühlen ... ähm ... ich meine ... Husten ... wird der Rest Ihres Teams schnell neugierig verstehen wiederkehrendes generisches Muster können Sie dies tun:
unterstützt durch
und der übergeordnete Typ:
Wichtige Punkte:
BEARBEITEN:
Ich habe einen Weg gefunden, um die falsche Objekterstellung zu umgehen. Fügen Sie dies zunächst jedem Builder hinzu:
Dann im Konstruktor für jeden Builder:
Die Kosten sind eine zusätzliche Klassendatei für die
new Object(){}
anonyme innere Klassequelle
Sie können in jeder Ihrer Klassen eine statische Factory-Methode erstellen:
Diese statische Factory-Methode würde dann den entsprechenden Builder zurückgeben. Sie können eine
GMOFacts.Builder
Erweiterung habenNutritionFacts.Builder
, das ist kein Problem. Das Problem hier wird sein, mit Sichtbarkeit umzugehen ...quelle
Der folgende IEEE-Beitrag Refined Fluent Builder in Java bietet eine umfassende Lösung für das Problem.
Es zerlegt die ursprüngliche Frage in zwei Unterprobleme: Vererbungsmangel und Quasi-Invarianz und zeigt, wie sich eine Lösung für diese beiden Unterprobleme für die Vererbungsunterstützung mit Code-Wiederverwendung im klassischen Builder-Muster in Java öffnet.
quelle
Ich habe eine übergeordnete, abstrakte generische Builder-Klasse erstellt, die zwei formale Typparameter akzeptiert. Erstens für den von build () zurückgegebenen Objekttyp, zweitens für den von jedem optionalen Parametersetzer zurückgegebenen Typ. Nachfolgend finden Sie Eltern- und Kinderklassen zur Veranschaulichung:
Dieser hat meine Bedürfnisse zur Zufriedenheit erfüllt.
quelle