Subkelas kelas Java Builder


133

Berikan artikel Dr Dobbs ini , dan Pola Builder secara khusus, bagaimana kita menangani kasus subklasifikasi Builder? Mengambil versi cut-down dari contoh di mana kita ingin subkelas untuk menambahkan label GMO, implementasi yang naif adalah:

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

Subkelas:

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

Sekarang, kita dapat menulis kode seperti ini:

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

Tetapi, jika kami salah memesan, semuanya gagal:

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

Masalahnya tentu saja NutritionFacts.Buildermengembalikan a NutritionFacts.Builder, bukan a GMOFacts.Builder, jadi bagaimana kita menyelesaikan masalah ini, atau apakah ada Pola yang lebih baik untuk digunakan?

Catatan: jawaban untuk pertanyaan serupa ini menawarkan kelas yang saya miliki di atas; pertanyaan saya adalah tentang masalah memastikan panggilan pembangun dalam urutan yang benar.


1
Saya pikir tautan berikut ini menggambarkan pendekatan yang baik: egalluzzo.blogspot.co.at/2010/06/...
stuXnet

1
Tetapi bagaimana Anda build()menghasilkan b.GMO(true).calories(100)?
Sridhar Sarnobat

Jawaban:


170

Anda dapat menyelesaikannya menggunakan obat generik. Saya pikir ini disebut "pola generik yang aneh berulang"

Buatlah tipe kembalinya metode pembangun kelas dasar argumen generik.

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

Sekarang instantiate pembangun basis dengan pembangun kelas turunan sebagai argumen generik.

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

2
Hmm, saya pikir saya harus (a) posting pertanyaan baru, (b) mendesain ulang dengan implementsbukan extends, atau (c) membuang segalanya. Saya sekarang memiliki kesalahan kompilasi yang aneh di mana leafBuilder.leaf().leaf()dan leafBuilder.mid().leaf()tidak apa-apa, tetapi leafBuilder.leaf().mid().leaf()gagal ...
Ken YN

11
@gkamal return (T) this;menghasilkan unchecked or unsafe operationsperingatan. Ini tidak mungkin untuk dihindari, bukan?
Dmitry Minkovsky

5
Untuk mengatasi unchecked castperingatan, lihat solusi yang disarankan di bawah ini di antara jawaban lain: stackoverflow.com/a/34741836/3114959
Stepan Vavra

8
Perhatikan bahwa Builder<T extends Builder>ini sebenarnya jenis mentah - ini seharusnya Builder<T extends Builder<T>>.
Boris the Spider

2
@ user2957378 Builderuntuk GMOFactsjuga perlu generik Builder<B extends Builder<B>> extends NutritionFacts.Builder<Builder>- dan pola ini dapat terus turun sebanyak level yang diperlukan. Jika Anda mendeklarasikan pembuat non-generik maka Anda tidak dapat memperpanjang polanya.
Boris the Spider

44

Hanya sebagai catatan, untuk menyingkirkan

unchecked or unsafe operations peringatan

untuk return (T) this;pernyataan seperti @dimadima dan @Thomas N. bicarakan, solusi berikut berlaku dalam kasus-kasus tertentu.

Buat abstractpembangun yang menyatakan tipe generik ( T extends Builderdalam hal ini) dan mendeklarasikan protected abstract T getThis()metode abstrak sebagai berikut:

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

Rujuk ke http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 untuk perincian lebih lanjut.


Mengapa build()metode mengembalikan NutrutionFacts di sini?
mvd

@ MB Karena ini adalah jawaban untuk pertanyaan? Dalam subtipe, Anda akan menimpanya sepertipublic GMOFacts build() { return new GMOFacts(this); }
Stepan Vavra

Masalahnya terjadi ketika kita ingin menambahkan anak ke-2 BuilderC extends BuilderBdan BuilderB extends BuilderAketika BuilderBtidakabstract
sosite

1
Ini bukan jawaban untuk pertanyaan, karena kelas dasar mungkin tidak abstrak!
Roland

"Buat abstrak pembuat yang menyatakan tipe generik" - bagaimana jika saya ingin menggunakan pembuat itu secara langsung?
Daisy

21

Didasarkan pada posting blog , pendekatan ini mengharuskan semua kelas non-daun menjadi abstrak, dan semua kelas daun harus final.

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

Kemudian, Anda memiliki beberapa kelas menengah yang memperluas kelas ini dan pembangunnya, dan sebanyak yang Anda butuhkan:

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

Dan, akhirnya kelas daun konkret yang dapat memanggil semua metode pembangun pada salah satu orang tuanya dalam urutan apa pun:

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

Kemudian, Anda dapat memanggil metode dalam urutan apa pun, dari salah satu kelas dalam hierarki:

public class Demo {
    LeafClass leaf = new LeafClass.Builder().baz(2).foo(1).bar(3).build();
}

Apakah Anda tahu mengapa kelas daun harus final? Saya ingin kelas konkret saya menjadi subclassable, tetapi belum menemukan cara untuk membuat kompiler memahami jenisnya B, selalu berubah menjadi kelas dasar.
David Ganster

Perhatikan bagaimana kelas Builder di LeafClass tidak mengikuti <T extends SomeClass, B extends SomeClass.Builder<T,B>> extends SomeClassParent.Builder<T,B>pola yang sama dengan kelas perantara SecondLevel, ia mendeklarasikan tipe spesifik sebagai gantinya. Anda tidak dapat membuat kelas sampai Anda mendapatkan daun menggunakan tipe spesifik, tetapi begitu Anda melakukannya, Anda tidak bisa memperpanjangnya lebih jauh karena Anda menggunakan tipe spesifik dan telah meninggalkan Pola Templat Berulang Curiously. Tautan ini mungkin membantu: angelikalanger.com/GenericsFAQ/FAQSections/…
Q23

7

Anda juga bisa mengganti calories()metode, dan membiarkannya mengembalikan builder yang diperluas. Ini mengkompilasi karena Java mendukung tipe pengembalian kovarian .

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);
        }
    }
    [...]
}

Ah, saya tidak tahu itu, karena saya berasal dari latar belakang C ++. Itu pendekatan yang berguna untuk contoh kecil ini, tetapi dengan kelas penuh mengulangi semua metode menjadi sakit, dan rasa sakit yang rawan kesalahan pada saat itu. +1 untuk mengajari saya sesuatu yang baru!
Ken YN

Tampaknya bagi saya bahwa ini tidak menyelesaikan apa pun. Alasan (IMO) untuk subklasifikasi induk adalah untuk menggunakan kembali metode orangtua tanpa menggantinya. Jika kelas hanyalah objek nilai tanpa logika nyata dalam metode builder kecuali untuk menetapkan nilai sederhana, maka memanggil metode induk dalam metode overriding memiliki nilai sedikit atau tidak ada nilai.
Pengembang Bung

Jawabannya memecahkan masalah yang dijelaskan dalam pertanyaan: kode menggunakan pembangun mengkompilasi dengan kedua pemesanan. Karena satu cara mengkompilasi dan yang lainnya tidak, saya kira pasti ada beberapa nilai.
Flavio

3

Ada juga cara lain untuk membuat kelas sesuai dengan Builderpola, yang sesuai dengan "Lebih suka komposisi daripada warisan".

Tentukan antarmuka, kelas induk yang Builderakan diwarisi:

public interface FactsBuilder<T> {

    public T calories(int val);
}

Implementasinya NutritionFactshampir sama (kecuali untuk Buildermengimplementasikan antarmuka 'FaktaBuilder'):

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

The Builderkelas anak harus memperpanjang antarmuka yang sama (kecuali pelaksanaan generik yang berbeda):

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

Perhatikan, itu NutritionFacts.Builderadalah bidang di dalam GMOFacts.Builder(disebut baseBuilder). Metode yang diterapkan dari metode FactsBuilderpanggilan antarmuka baseBuilderdengan nama yang sama:

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

Ada juga perubahan besar dalam konstruktor GMOFacts(Builder builder). Panggilan pertama dalam konstruktor ke konstruktor kelas induk harus lulus yang sesuai NutritionFacts.Builder:

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

Implementasi penuh GMOFactskelas:

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

3

Contoh 3 tingkat lengkap dari pewarisan berganda akan terlihat seperti ini :

(Untuk versi dengan copy constructor untuk pembangun lihat contoh kedua di bawah)

Tingkat pertama - orang tua (berpotensi abstrak)

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

Tingkat kedua

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

Tingkat ketiga

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

Dan contoh penggunaannya

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


Versi yang sedikit lebih lama menampilkan copy constructor untuk pembangun:

Tingkat pertama - orang tua (berpotensi abstrak)

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

Tingkat kedua

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

Tingkat ketiga

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

Dan contoh penggunaannya

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

2

Jika Anda tidak ingin membuka mata Anda pada braket sudut atau tiga, atau mungkin tidak merasa Anda ... umm ... maksud saya ... batuk ... seluruh tim Anda akan dengan cepat memahami dengan rasa ingin tahu pola generik berulang, Anda dapat melakukan ini:

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

didukung oleh

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

dan tipe induk:

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

Poin-poin penting:

  • Enkapsulasi objek dalam pembangun sehingga warisan mencegah Anda dari menyetel bidang pada objek yang disimpan dalam tipe induk
  • Panggilan untuk memastikan super bahwa logika (jika ada) ditambahkan ke metode pembangun tipe super dipertahankan dalam sub jenis.
  • Sisi bawah adalah penciptaan objek palsu di kelas induk (es) ... Tapi lihat di bawah ini untuk cara membersihkannya
  • Sisi atas jauh lebih mudah dipahami sekilas, dan tidak ada sifat transfer konstruktor verbose.
  • Jika Anda memiliki beberapa utas mengakses objek pembangun Anda ... Saya rasa saya senang saya bukan Anda :).

EDIT:

Saya menemukan jalan di sekitar penciptaan objek palsu. Pertama tambahkan ini ke setiap pembangun:

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

Kemudian di konstruktor untuk setiap pembangun:

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

Biaya adalah file kelas tambahan untuk new Object(){}kelas dalam anonim


1

Satu hal yang dapat Anda lakukan adalah membuat metode pabrik statis di setiap kelas Anda:

NutritionFacts.newBuilder()
GMOFacts.newBuilder()

Metode pabrik statis ini kemudian akan mengembalikan pembangun yang sesuai. Anda dapat memiliki GMOFacts.Builderekstensi NutritionFacts.Builder, itu bukan masalah. Masalahnya di sini adalah untuk menangani visibilitas ...


0

Kontribusi IEEE berikut Refined Fluent Builder di Java memberikan solusi komprehensif untuk masalah ini.

Ini membedah pertanyaan asli menjadi dua sub-masalah kekurangan warisan dan invasi kuasi dan menunjukkan bagaimana solusi untuk dua sub-masalah ini terbuka untuk dukungan warisan dengan penggunaan kembali kode dalam pola pembangun klasik di Jawa.


Jawaban ini tidak mengandung informasi apa pun untuk membantu, tidak mengandung setidaknya ringkasan jawaban yang diberikan dalam tautan dan mengarah ke tautan yang memerlukan login.
Sonata

Jawaban ini terhubung ke publikasi konferensi yang ditinjau oleh rekan sejawat dengan otoritas penerbitan resmi dan penerbitan resmi serta prosedur berbagi.
mc00x1

0

Saya membuat induk, kelas pembangun generik abstrak yang menerima dua parameter tipe formal. Pertama adalah untuk tipe objek yang dikembalikan oleh build (), yang kedua adalah tipe yang dikembalikan oleh setiap setter parameter opsional. Di bawah ini adalah kelas orangtua dan anak untuk tujuan ilustrasi:

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

Yang ini telah memenuhi kebutuhan saya untuk kepuasan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.