Podklasy klasy Java Builder

134

Podaj ten artykuł Dr Dobbs , aw szczególności wzorzec Builder, jak radzimy sobie z przypadkiem podklasy Buildera? Biorąc okrojoną wersję przykładu, w którym chcemy podklasę, aby dodać etykietowanie GMO, naiwna implementacja byłaby taka:

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;                                                                                             
    }                                                                                                                            
}

Podklasa:

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;                                                                                                 
    }                                                                                                                            
}

Teraz możemy napisać taki kod:

GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);

Ale jeśli źle zamówimy, wszystko zawiedzie:

GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);

Problem polega oczywiście na tym, że NutritionFacts.Builderzwraca a NutritionFacts.Builder, a nie a GMOFacts.Builder, więc jak rozwiązać ten problem, czy też istnieje lepszy wzorzec do użycia?

Uwaga: ta odpowiedź na podobne pytanie zawiera klasy, które mam powyżej; moje pytanie dotyczy problemu z zapewnieniem, że wywołania budowniczych są we właściwej kolejności.

Ken YN
źródło
1
Myślę, że poniższy link opisuje dobre podejście: egalluzzo.blogspot.co.at/2010/06/…
stuXnet
1
Ale w jaki sposób uzyskujesz build()wynik b.GMO(true).calories(100)?
Sridhar Sarnobat

Odpowiedzi:

173

Możesz go rozwiązać za pomocą typów ogólnych. Myślę, że to się nazywa „Ciekawie powtarzające się ogólne wzorce”

Ustaw zwracany typ metod konstruktora klasy bazowej jako argument ogólny.

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;
    }
}

Teraz utwórz wystąpienie konstruktora podstawowego z konstruktorem klas pochodnych jako argumentem ogólnym.

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
źródło
2
Hmm, myślę, że będę musiał (a) opublikować nowe pytanie, (b) przeprojektować za pomocą implementszamiast extendslub (c) wyrzucić wszystko. Mam teraz dziwny błąd kompilacji, gdzie leafBuilder.leaf().leaf()i leafBuilder.mid().leaf()jest OK, ale leafBuilder.leaf().mid().leaf()nie udaje mi się ...
Ken YN,
11
@gkamal return (T) this;powoduje wyświetlenie unchecked or unsafe operationsostrzeżenia. Tego nie da się uniknąć, prawda?
Dmitry Minkovsky
5
Aby rozwiązać unchecked castostrzeżenie, zobacz rozwiązanie sugerowane poniżej, obok innych odpowiedzi: stackoverflow.com/a/34741836/3114959
Stepan Vavra
8
Zauważ, że w Builder<T extends Builder>rzeczywistości jest to typ surowy - tak powinno być Builder<T extends Builder<T>>.
Boris the Spider
2
@ user2957378 Builderdla GMOFactsrównież muszą mieć charakter ogólny Builder<B extends Builder<B>> extends NutritionFacts.Builder<Builder>- i ten wzór można kontynuować w dół jak wielu poziomach, jak wymagane. Jeśli zadeklarujesz nieogólny program budujący, nie możesz rozszerzyć wzorca.
Boris the Spider
46

Tak dla porządku, żeby pozbyć się pliku

unchecked or unsafe operations ostrzeżenie

w przypadku return (T) this;stwierdzenia, o którym mówią @dimadima i @Thomas N., w niektórych przypadkach ma zastosowanie następujące rozwiązanie.

Utwórz abstractkonstruktora, który deklaruje typ ogólny ( T extends Builderw tym przypadku) i zadeklaruj protected abstract T getThis()metodę abstrakcyjną w następujący sposób:

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); }
}

Więcej informacji można znaleźć pod adresem http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 .

Stepan Vavra
źródło
Dlaczego build()metoda zwraca tutaj NutrutionFacts?
mvd,
@mvd Bo to jest odpowiedź na pytanie? W podtypach zastąpisz to, na przykładpublic GMOFacts build() { return new GMOFacts(this); }
Stepan Vavra
Problem pojawia się, gdy chcemy dodać drugie dziecko, BuilderC extends BuilderBa BuilderB extends BuilderAkiedy BuilderBnieabstract
tak
1
To nie jest odpowiedź na pytanie, ponieważ klasa bazowa może nie być abstrakcyjna!
Roland
„Zrób abstrakcję konstruktora, który deklaruje typ ogólny” - a co jeśli chciałbym użyć tego konstruktora bezpośrednio?
daisy
21

Opierając się na poście na blogu , podejście to wymaga, aby wszystkie klasy niebędące liśćmi były abstrakcyjne, a wszystkie klasy liści muszą być ostateczne.

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;
        }
    }
}

Następnie masz klasę pośrednią, która rozszerza tę klasę i jej konstruktora, a także tyle, ile potrzebujesz:

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;
        }
    }
}

I wreszcie konkretna klasa liścia, która może wywoływać wszystkie metody konstruktora dowolnego ze swoich elementów nadrzędnych w dowolnej kolejności:

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;
        }
    }
}

Następnie możesz wywoływać metody w dowolnej kolejności, z dowolnej klasy w hierarchii:

public class Demo {
    LeafClass leaf = new LeafClass.Builder().baz(2).foo(1).bar(3).build();
}
P23
źródło
Czy wiesz, dlaczego zajęcia z liśćmi muszą być ostateczne? Chciałbym, aby moje konkretne klasy były podklasy, ale nie znalazłem sposobu, aby kompilator zrozumiał typ B, zawsze okazuje się, że jest to klasa bazowa.
David Ganster,
Zwróć uwagę, że klasa Builder w LeafClass nie jest zgodna z tym samym <T extends SomeClass, B extends SomeClass.Builder<T,B>> extends SomeClassParent.Builder<T,B>wzorcem, co klasa pośrednia SecondLevel, zamiast tego deklaruje określone typy. Nie możesz zainicjować klasy, dopóki nie dojdziesz do liścia przy użyciu określonych typów, ale kiedy już to zrobisz, nie możesz jej dalej rozszerzyć, ponieważ używasz określonych typów i porzuciłeś Curiously Recurring Template Pattern. Ten link może pomóc: angelikalanger.com/GenericsFAQ/FAQSections/…
Q23
7

Możesz także przesłonić calories()metodę i pozwolić jej zwrócić rozszerzający konstruktor. Kompiluje się, ponieważ Java obsługuje kowariantne typy zwracane .

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
źródło
Ach, nie wiedziałem tego, ponieważ pochodzę z C ++. To przydatne podejście w przypadku tego małego przykładu, ale w przypadku pełnowymiarowej klasy powtarzanie wszystkich metod staje się uciążliwe i podatne na błędy. Jednak +1 za nauczenie mnie czegoś nowego!
Ken YN
Wydawałoby mi się, że to niczego nie rozwiązuje. Powodem (IMO) podklasyfikowania elementu nadrzędnego jest ponowne użycie metod nadrzędnych bez ich zastępowania. Jeśli klasy są po prostu obiektami wartości bez rzeczywistej logiki w metodach konstruktora, z wyjątkiem ustawienia prostej wartości, to wywołanie metody nadrzędnej w metodzie przesłaniającej ma niewielką lub żadną wartość.
Deweloper Dude
Odpowiedź rozwiązuje problem opisany w pytaniu: kod korzystający z kreatora kompiluje się z obydwoma porządkami. Ponieważ jeden sposób kompiluje, a drugi nie, myślę, że w końcu musi być jakaś wartość.
Flavio
3

Istnieje również inny sposób tworzenia klas według Builderwzorca, który jest zgodny z zasadą „Preferuj kompozycję zamiast dziedziczenia”.

Zdefiniuj interfejs, który Builderodziedziczy klasa nadrzędna :

public interface FactsBuilder<T> {

    public T calories(int val);
}

Implementacja NutritionFactsjest prawie taka sama (z wyjątkiem Builderimplementacji interfejsu „FactsBuilder”):

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;
    }
}

BuilderKlasy dziecka powinni przedłużyć ten sam interfejs (z wyjątkiem innej realizacji rodzajowe):

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;
    }
}

Zauważ, że NutritionFacts.Builderjest to pole wewnątrz GMOFacts.Builder(nazywane baseBuilder). Metoda zaimplementowana z metody FactsBuilderwywołań interfejsu baseBuildero tej samej nazwie:

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

Jest też duża zmiana w konstruktorze GMOFacts(Builder builder). Pierwsze wywołanie w konstruktorze konstruktora klasy nadrzędnej powinno zostać przekazane odpowiednio NutritionFacts.Builder:

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

Pełna realizacja GMOFactsklasy:

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
źródło
3

Pełny przykład 3 poziomu dziedziczenia wielu konstruktorów wyglądałby następująco :

(W przypadku wersji z konstruktorem kopiującym dla konstruktora zobacz drugi przykład poniżej)

Poziom pierwszy - rodzic (potencjalnie abstrakcyjny)

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;
        }
    }
}

Drugi poziom

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;
        }
    }
}

Trzeci poziom

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;
        }
    }
}

I przykład użycia

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);
    }
}


Nieco dłuższa wersja z konstruktorem kopiującym dla konstruktora:

Poziom pierwszy - rodzic (potencjalnie abstrakcyjny)

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;
        }
    }
}

Drugi poziom

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;
        }
    }
}

Trzeci poziom

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;
        }
    }
}

I przykład użycia

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
źródło
2

Jeśli nie chcesz wystawiać oczu na kątownik lub trzy, a może nie czujesz cię ... hm ... to znaczy ... kaszel ... reszta twojego zespołu szybko zrozumie z zaciekawieniem powtarzający się wzorzec generyczny, możesz to zrobić:

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");
  }
}

wspierany przez

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();
  }
}

i typ rodzica:

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 + '\'' +
        '}';
  }
}

Kluczowe punkty:

  • Hermetyzuj obiekt w konstruktorze, aby dziedziczenie uniemożliwiało ustawienie pola w obiekcie przechowywanym w typie nadrzędnym
  • Wywołania super zapewniają, że logika (jeśli istnieje) dodana do metod konstruktora supertypów zostanie zachowana w typach podrzędnych.
  • Wadą jest fałszywe tworzenie obiektów w klasach nadrzędnych ... Ale zobacz poniżej, jak to naprawić
  • Widok z góry jest znacznie łatwiejszy do zrozumienia na pierwszy rzut oka i żaden konstruktor gadatliwy nie przenosi właściwości.
  • Jeśli masz wiele wątków uzyskujących dostęp do obiektów kreatora ... Myślę, że cieszę się, że nie jestem tobą :).

EDYTOWAĆ:

Znalazłem sposób na obejście fałszywego tworzenia obiektów. Najpierw dodaj to do każdego konstruktora:

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

Następnie w konstruktorze dla każdego konstruktora:

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

Koszt to dodatkowy plik klasy dla new Object(){}anonimowej klasy wewnętrznej

Gus
źródło
1

Jedną z rzeczy, które możesz zrobić, jest utworzenie statycznej metody fabrycznej w każdej z klas:

NutritionFacts.newBuilder()
GMOFacts.newBuilder()

Ta statyczna metoda fabryki zwróci następnie odpowiedni konstruktor. Możesz mieć GMOFacts.Builderprzedłużenie a NutritionFacts.Builder, to nie jest problem. Problemem będzie tutaj poradzenie sobie z widocznością ...

fge
źródło
0

Poniższy wkład w projekt IEEE Refined Fluent Builder in Java przedstawia kompleksowe rozwiązanie problemu.

Rozbija pierwotne pytanie na dwa podproblemy: niedobór dziedziczenia i quasi-niezmienniczość i pokazuje, w jaki sposób otwiera się rozwiązanie tych dwóch podproblemów do obsługi dziedziczenia z ponownym wykorzystaniem kodu w klasycznym wzorcu konstruktora w Javie.

mc00x1
źródło
Ta odpowiedź nie zawiera żadnych informacji pomocnych, nie zawiera przynajmniej streszczenia odpowiedzi udzielonej w linku i prowadzi do linku wymagającego logowania.
Sonata
Ta odpowiedź prowadzi do recenzowanej publikacji konferencyjnej z oficjalnym organem wydawniczym oraz oficjalną procedurą publikowania i udostępniania.
mc00x1
0

Utworzyłem nadrzędną, abstrakcyjną generyczną klasę konstruktora, która akceptuje dwa formalne parametry typu. Pierwsza dotyczy typu obiektu zwracanego przez funkcję build (), druga jest typem zwracanym przez każdy opcjonalny ustawiacz parametrów. Poniżej znajdują się klasy dla rodziców i dzieci w celach ilustracyjnych:

// **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;
    }
}

Ten spełnił moje potrzeby do satysfakcji.

Jose Quijada
źródło