Unterklasse einer Java Builder-Klasse

133

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.Buildera 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.

Ken YN
quelle
1
Ich denke, der folgende Link beschreibt einen guten Ansatz: egalluzzo.blogspot.co.at/2010/06/…
stuXnet
1
Aber wie macht man build()die Ausgabe von b.GMO(true).calories(100)?
Sridhar Sarnobat

Antworten:

170

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.

public class NutritionFacts {

    private final int calories;

    public static class Builder<T extends Builder<T>> {

        private int calories = 0;

        public Builder() {}

        public T calories(int val) {
            calories = val;
            return (T) this;
        }

        public NutritionFacts build() { return new NutritionFacts(this); }
    }

    protected NutritionFacts(Builder<?> builder) {
        calories = builder.calories;
    }
}

Instanziieren Sie nun den Basis-Builder mit dem abgeleiteten Klassen-Builder als generischem Argument.

public class GMOFacts extends NutritionFacts {

    private final boolean hasGMO;

    public static class Builder extends NutritionFacts.Builder<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;
    }
}
gkamal
quelle
2
Hmm, ich denke ich muss entweder (a) eine neue Frage stellen, (b) neu gestalten mit implementsanstatt extendsoder (c) alles wegwerfen. Ich habe jetzt einen seltsamen Kompilierungsfehler, wo leafBuilder.leaf().leaf()und leafBuilder.mid().leaf()ist OK, aber leafBuilder.leaf().mid().leaf()schlägt fehl ...
Ken YN
11
@gkamal return (T) this;führt zu einer unchecked or unsafe operationsWarnung. Das ist unmöglich zu vermeiden, oder?
Dmitry Minkovsky
5
Um die unchecked castWarnung zu lösen , lesen Sie die unten
Stepan Vavra
8
Beachten Sie, dass dies Builder<T extends Builder>tatsächlich ein Rohtyp ist - dies sollte sein Builder<T extends Builder<T>>.
Boris die Spinne
2
@ user2957378 Das Builderfür GMOFactsmuss auch generisch sein Builder<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.
Boris die Spinne
44

Nur für die Aufzeichnung, um das loszuwerden

unchecked or unsafe operations Warnung

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 abstractden Builder, der den generischen Typ deklariert ( T extends Builderin diesem Fall), und deklarieren Sie die protected abstract T getThis()abstrakte Methode wie folgt:

public abstract static class Builder<T extends Builder<T>> {

    private int calories = 0;

    public Builder() {}

    /** The solution for the unchecked cast warning. */
    public abstract T getThis();

    public T calories(int val) {
        calories = val;

        // no cast needed
        return getThis();
    }

    public NutritionFacts build() { return new NutritionFacts(this); }
}

Weitere Informationen finden Sie unter http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 .

Stepan Vavra
quelle
Warum gibt die build()Methode die NutrutionFacts hier zurück?
MVD
@mvd Weil dies eine Antwort auf die Frage ist? In den Untertypen überschreiben Sie es wiepublic GMOFacts build() { return new GMOFacts(this); }
Stepan Vavra
Das Problem tritt auf, wenn wir das zweite Kind hinzufügen möchten BuilderC extends BuilderBund BuilderB extends BuilderAwenn dies BuilderBnicht der abstract
Fall
1
Dies ist keine Antwort auf die Frage, da die Basisklasse möglicherweise nicht abstrakt ist!
Roland
"Machen Sie abstrakt zum Builder, der den generischen Typ deklariert" - was wäre, wenn ich diesen Builder direkt verwenden wollte?
Gänseblümchen
21

Basierend auf einem Blog-Beitrag erfordert dieser Ansatz, dass alle Nicht-Blatt-Klassen abstrakt und alle Blatt-Klassen endgültig sind.

public abstract class TopLevel {
    protected int foo;
    protected TopLevel() {
    }
    protected static abstract class Builder
        <T extends TopLevel, B extends Builder<T, B>> {
        protected T object;
        protected B thisObject;
        protected abstract T createObject();
        protected abstract B thisObject();
        public Builder() {
            object = createObject();
            thisObject = thisObject();
        }
        public B foo(int foo) {
            object.foo = foo;
            return thisObject;
        }
        public T build() {
            return object;
        }
    }
}

Dann haben Sie eine Zwischenklasse, die diese Klasse und ihren Builder erweitert, und so viele weitere, wie Sie benötigen:

public abstract class SecondLevel extends TopLevel {
    protected int bar;
    protected static abstract class Builder
        <T extends SecondLevel, B extends Builder<T, B>> extends TopLevel.Builder<T, B> {
        public B bar(int bar) {
            object.bar = bar;
            return thisObject;
        }
    }
}

Und schließlich eine konkrete Blattklasse, die alle Builder-Methoden für jeden ihrer Elternteile in beliebiger Reihenfolge aufrufen kann:

public final class LeafClass extends SecondLevel {
    private int baz;
    public static final class Builder extends SecondLevel.Builder<LeafClass,Builder> {
        protected LeafClass createObject() {
            return new LeafClass();
        }
        protected Builder thisObject() {
            return this;
        }
        public Builder baz(int baz) {
            object.baz = baz;
            return thisObject;
        }
    }
}

Anschließend können Sie die Methoden in beliebiger Reihenfolge aus einer der Klassen in der Hierarchie aufrufen:

public class Demo {
    LeafClass leaf = new LeafClass.Builder().baz(2).foo(1).bar(3).build();
}
Q23
quelle
Wissen Sie, warum die Blattklassen endgültig sein müssen? Ich möchte, dass meine konkreten Klassen unterklassifizierbar sind, habe aber keinen Weg gefunden, den Compiler dazu zu bringen, den Typ zu verstehen B. Es stellt sich immer als Basisklasse heraus.
David Ganster
Beachten Sie, dass die Builder-Klasse in LeafClass nicht dem gleichen <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/…
Q23
7

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 .

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 Builder calories(int val)
        { super.calories(val); return this; }
        public GMOFacts build() {
            return new GMOFacts(this);
        }
    }
    [...]
}
Flavio
quelle
Ah, das wusste ich nicht, da ich aus einem C ++ - Hintergrund komme. Das ist ein nützlicher Ansatz für dieses kleine Beispiel, aber wenn eine vollständige Klasse alle Methoden wiederholt, wird dies zu einem Schmerz und zu einem fehleranfälligen Schmerz. +1, um mir etwas Neues beizubringen!
Ken YN
Es scheint mir, dass dies nichts löst. Der Grund (IMO) für die Unterklassifizierung des übergeordneten Elements besteht darin, die übergeordneten Methoden wiederzuverwenden, ohne sie zu überschreiben. Wenn die Klassen einfach Wertobjekte ohne echte Logik in den Builder-Methoden sind, außer um einen einfachen Wert festzulegen, hat der Aufruf der übergeordneten Methode in der überschreibenden Methode wenig bis gar keinen Wert.
Entwickler Dude
Die Antwort löst das in der Frage beschriebene Problem: Der Code, der den Builder verwendet, wird mit beiden Ordnungen kompiliert. Da der eine Weg kompiliert und der andere nicht, muss es doch einen Wert geben.
Flavio
3

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 Buildererbt:

public interface FactsBuilder<T> {

    public T calories(int val);
}

Die Implementierung von NutritionFactsist fast dieselbe (mit Ausnahme der BuilderImplementierung der 'FactsBuilder'-Schnittstelle):

public class NutritionFacts {

    private final int calories;

    public static class Builder implements FactsBuilder<Builder> {
        private int calories = 0;

        public Builder() {
        }

        @Override
        public Builder calories(int val) {
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    protected NutritionFacts(Builder builder) {
        calories = builder.calories;
    }
}

Die Buildereiner untergeordneten Klasse sollte dieselbe Schnittstelle erweitern (mit Ausnahme unterschiedlicher generischer Implementierungen):

public static class Builder implements FactsBuilder<Builder> {
    NutritionFacts.Builder baseBuilder;

    private boolean hasGMO = false;

    public Builder() {
        baseBuilder = new NutritionFacts.Builder();
    }

    public Builder GMO(boolean val) {
        hasGMO = val;
        return this;
    }

    public GMOFacts build() {
        return new GMOFacts(this);
    }

    @Override
    public Builder calories(int val) {
        baseBuilder.calories(val);
        return this;
    }
}

Beachten Sie, dass dies NutritionFacts.Builderein Feld im Inneren ist GMOFacts.Builder(aufgerufen baseBuilder). Die von der gleichnamigen Methode der FactsBuilderSchnittstellenaufrufe implementierte baseBuilderMethode:

@Override
public Builder calories(int val) {
    baseBuilder.calories(val);
    return this;
}

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 werden NutritionFacts.Builder:

protected GMOFacts(Builder builder) {
    super(builder.baseBuilder);
    hasGMO = builder.hasGMO;
}

Die vollständige Implementierung der GMOFactsKlasse:

public class GMOFacts extends NutritionFacts {

    private final boolean hasGMO;

    public static class Builder implements FactsBuilder<Builder> {
        NutritionFacts.Builder baseBuilder;

        private boolean hasGMO = false;

        public Builder() {
        }

        public Builder GMO(boolean val) {
            hasGMO = val;
            return this;
        }

        public GMOFacts build() {
            return new GMOFacts(this);
        }

        @Override
        public Builder calories(int val) {
            baseBuilder.calories(val);
            return this;
        }
    }

    protected GMOFacts(Builder builder) {
        super(builder.baseBuilder);
        hasGMO = builder.hasGMO;
    }
}
R. Zagórski
quelle
3

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)

import lombok.ToString;

@ToString
@SuppressWarnings("unchecked")
public abstract class Class1 {
    protected int f1;

    public static class Builder<C extends Class1, B extends Builder<C, B>> {
        C obj;

        protected Builder(C constructedObj) {
            this.obj = constructedObj;
        }

        B f1(int f1) {
            obj.f1 = f1;
            return (B)this;
        }

        C build() {
            return obj;
        }
    }
}

Zweites Level

import lombok.ToString;

@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class2 extends Class1 {
    protected int f2;

    public static class Builder<C extends Class2, B extends Builder<C, B>> extends Class1.Builder<C, B> {
        public Builder() {
            this((C) new Class2());
        }

        protected Builder(C obj) {
            super(obj);
        }

        B f2(int f2) {
            obj.f2 = f2;
            return (B)this;
        }
    }
}

Drittes Level

import lombok.ToString;

@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class3 extends Class2 {
    protected int f3;

    public static class Builder<C extends Class3, B extends Builder<C, B>> extends Class2.Builder<C, B> {
        public Builder() {
            this((C) new Class3());
        }

        protected Builder(C obj) {
            super(obj);
        }

        B f3(int f3) {
            obj.f3 = f3;
            return (B)this;
        }
    }
}

Und ein Anwendungsbeispiel

public class Test {
    public static void main(String[] args) {
        Class2 b1 = new Class2.Builder<>().f1(1).f2(2).build();
        System.out.println(b1);
        Class2 b2 = new Class2.Builder<>().f2(2).f1(1).build();
        System.out.println(b2);

        Class3 c1 = new Class3.Builder<>().f1(1).f2(2).f3(3).build();
        System.out.println(c1);
        Class3 c2 = new Class3.Builder<>().f3(3).f1(1).f2(2).build();
        System.out.println(c2);
        Class3 c3 = new Class3.Builder<>().f3(3).f2(2).f1(1).build();
        System.out.println(c3);
        Class3 c4 = new Class3.Builder<>().f2(2).f3(3).f1(1).build();
        System.out.println(c4);
    }
}


Eine etwas längere Version mit einem Kopierkonstruktor für den Builder:

Erste Ebene - Eltern (möglicherweise abstrakt)

import lombok.ToString;

@ToString
@SuppressWarnings("unchecked")
public abstract class Class1 {
    protected int f1;

    public static class Builder<C extends Class1, B extends Builder<C, B>> {
        C obj;

        protected void setObj(C obj) {
            this.obj = obj;
        }

        protected void copy(C obj) {
            this.f1(obj.f1);
        }

        B f1(int f1) {
            obj.f1 = f1;
            return (B)this;
        }

        C build() {
            return obj;
        }
    }
}

Zweites Level

import lombok.ToString;

@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class2 extends Class1 {
    protected int f2;

    public static class Builder<C extends Class2, B extends Builder<C, B>> extends Class1.Builder<C, B> {
        public Builder() {
            setObj((C) new Class2());
        }

        public Builder(C obj) {
            this();
            copy(obj);
        }

        @Override
        protected void copy(C obj) {
            super.copy(obj);
            this.f2(obj.f2);
        }

        B f2(int f2) {
            obj.f2 = f2;
            return (B)this;
        }
    }
}

Drittes Level

import lombok.ToString;

@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class3 extends Class2 {
    protected int f3;

    public static class Builder<C extends Class3, B extends Builder<C, B>> extends Class2.Builder<C, B> {
        public Builder() {
            setObj((C) new Class3());
        }

        public Builder(C obj) {
            this();
            copy(obj);
        }

        @Override
        protected void copy(C obj) {
            super.copy(obj);
            this.f3(obj.f3);
        }

        B f3(int f3) {
            obj.f3 = f3;
            return (B)this;
        }
    }
}

Und ein Anwendungsbeispiel

public class Test {
    public static void main(String[] args) {
        Class3 c4 = new Class3.Builder<>().f2(2).f3(3).f1(1).build();
        System.out.println(c4);

        // Class3 builder copy
        Class3 c42 = new Class3.Builder<>(c4).f2(12).build();
        System.out.println(c42);
        Class3 c43 = new Class3.Builder<>(c42).f2(22).f1(11).build();
        System.out.println(c43);
        Class3 c44 = new Class3.Builder<>(c43).f3(13).f1(21).build();
        System.out.println(c44);
    }
}
v0rin
quelle
2

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:

public class TestInheritanceBuilder {
  public static void main(String[] args) {
    SubType.Builder builder = new SubType.Builder();
    builder.withFoo("FOO").withBar("BAR").withBaz("BAZ");
    SubType st = builder.build();
    System.out.println(st.toString());
    builder.withFoo("BOOM!").withBar("not getting here").withBaz("or here");
  }
}

unterstützt durch

public class SubType extends ParentType {
  String baz;
  protected SubType() {}

  public static class Builder extends ParentType.Builder {
    private SubType object = new SubType();

    public Builder withBaz(String baz) {
      getObject().baz = baz;
      return this;
    }

    public Builder withBar(String bar) {
      super.withBar(bar);
      return this;
    }

    public Builder withFoo(String foo) {
      super.withFoo(foo);
      return this;
    }

    public SubType build() {
      // or clone or copy constructor if you want to stamp out multiple instances...
      SubType tmp = getObject();
      setObject(new SubType());
      return tmp;
    }

    protected SubType getObject() {
      return object;
    }

    private void setObject(SubType object) {
      this.object = object;
    }
  }

  public String toString() {
    return "SubType2{" +
        "baz='" + baz + '\'' +
        "} " + super.toString();
  }
}

und der übergeordnete Typ:

public class ParentType {
  String foo;
  String bar;

  protected ParentType() {}

  public static class Builder {
    private ParentType object = new ParentType();

    public ParentType object() {
      return getObject();
    }

    public Builder withFoo(String foo) {
      if (!"foo".equalsIgnoreCase(foo)) throw new IllegalArgumentException();
      getObject().foo = foo;
      return this;
    }

    public Builder withBar(String bar) {
      getObject().bar = bar;
      return this;
    }

    protected ParentType getObject() {
      return object;
    }

    private void setObject(ParentType object) {
      this.object = object;
    }

    public ParentType build() {
      // or clone or copy constructor if you want to stamp out multiple instances...
      ParentType tmp = getObject();
      setObject(new ParentType());
      return tmp;
    }
  }

  public String toString() {
    return "ParentType2{" +
        "foo='" + foo + '\'' +
        ", bar='" + bar + '\'' +
        '}';
  }
}

Wichtige Punkte:

  • Kapselung des Objekts im Builder, sodass Sie durch Vererbung nicht das Feld für das im übergeordneten Typ enthaltene Objekt festlegen können
  • Aufrufe von super stellen sicher, dass die Logik (falls vorhanden), die den Methoden zum Erstellen von Supertypen hinzugefügt wurde, in den Untertypen beibehalten wird.
  • Der Nachteil ist die falsche Objekterstellung in den übergeordneten Klassen ... Aber siehe unten für eine Möglichkeit, dies zu bereinigen
  • Die Oberseite ist auf einen Blick viel einfacher zu verstehen, und kein ausführlicher Konstruktor überträgt Eigenschaften.
  • Wenn Sie mehrere Threads haben, die auf Ihre Builder-Objekte zugreifen ... Ich bin froh, dass ich nicht Sie bin :).

BEARBEITEN:

Ich habe einen Weg gefunden, um die falsche Objekterstellung zu umgehen. Fügen Sie dies zunächst jedem Builder hinzu:

private Class whoAmI() {
  return new Object(){}.getClass().getEnclosingMethod().getDeclaringClass();
}

Dann im Konstruktor für jeden Builder:

  if (whoAmI() == this.getClass()) {
    this.obj = new ObjectToBuild();
  }

Die Kosten sind eine zusätzliche Klassendatei für die new Object(){}anonyme innere Klasse

Gus
quelle
1

Sie können in jeder Ihrer Klassen eine statische Factory-Methode erstellen:

NutritionFacts.newBuilder()
GMOFacts.newBuilder()

Diese statische Factory-Methode würde dann den entsprechenden Builder zurückgeben. Sie können eine GMOFacts.BuilderErweiterung haben NutritionFacts.Builder, das ist kein Problem. Das Problem hier wird sein, mit Sichtbarkeit umzugehen ...

fge
quelle
0

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.

mc00x1
quelle
Diese Antwort enthält keine hilfreichen Informationen, enthält nicht mindestens eine Zusammenfassung der im Link angegebenen Antwort und führt zu einem Link, für den eine Anmeldung erforderlich ist.
Sonate
Diese Antwort verweist auf eine von Experten begutachtete Konferenzpublikation mit einer offiziellen Veröffentlichungsbehörde und einem offiziellen Veröffentlichungs- und Freigabeverfahren.
mc00x1
0

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:

// **Parent**
public abstract static class Builder<T, U extends Builder<T, U>> {
    // Required parameters
    private final String name;

    // Optional parameters
    private List<String> outputFields = null;


    public Builder(String pName) {
        name = pName;
    }

    public U outputFields(List<String> pOutFlds) {
        outputFields = new ArrayList<>(pOutFlds);
        return getThis();
    }


    /**
     * This helps avoid "unchecked warning", which would forces to cast to "T" in each of the optional
     * parameter setters..
     * @return
     */
    abstract U getThis();

    public abstract T build();



    /*
     * Getters
     */
    public String getName() {
        return name;
    }
}

 // **Child**
 public static class Builder extends AbstractRule.Builder<ContextAugmentingRule, ContextAugmentingRule.Builder> {
    // Required parameters
    private final Map<String, Object> nameValuePairsToAdd;

    // Optional parameters
    private String fooBar;


    Builder(String pName, Map<String, String> pNameValPairs) {
        super(pName);
        /**
         * Must do this, in case client code (I.e. JavaScript) is re-using
         * the passed in for multiple purposes. Doing {@link Collections#unmodifiableMap(Map)}
         * won't caught it, because the backing Map passed by client prior to wrapping in
         * unmodifiable Map can still be modified.
         */
        nameValuePairsToAdd = new HashMap<>(pNameValPairs);
    }

    public Builder fooBar(String pStr) {
        fooBar = pStr;
        return this;
    }


    @Override
    public ContextAugmentingRule build() {
        try {
            Rule r = new ContextAugmentingRule(this);
            storeInRuleByNameCache(r);
            return (ContextAugmentingRule) r;
        } catch (RuleException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Override
    Builder getThis() {
        return this;
    }
}

Dieser hat meine Bedürfnisse zur Zufriedenheit erfüllt.

Jose Quijada
quelle