คลาสย่อยคลาส Java Builder


133

ให้บทความ Dr Dobbs นี้และรูปแบบตัวสร้างโดยเฉพาะเราจะจัดการกรณีของคลาสย่อยของ Builder ได้อย่างไร? การใช้ตัวอย่างแบบย่อส่วนที่เราต้องการให้คลาสย่อยเพิ่มการติดฉลากจีเอ็มโอการใช้งานที่ไร้เดียงสาจะเป็น:

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

ซับคลาส:

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

ตอนนี้เราสามารถเขียนโค้ดได้ดังนี้:

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

แต่ถ้าเราสั่งผิดทุกอย่างจะล้มเหลว:

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

ปัญหาคือแน่นอนว่าNutritionFacts.Builderส่งคืน a NutritionFacts.Builderไม่ใช่ a GMOFacts.Builderแล้วเราจะแก้ปัญหานี้อย่างไรหรือมีรูปแบบที่ดีกว่าให้ใช้?

หมายเหตุ: คำตอบสำหรับคำถามที่คล้ายกันนี้นำเสนอชั้นเรียนที่ฉันมีด้านบน คำถามของฉันเกี่ยวกับปัญหาในการตรวจสอบว่าการโทรของผู้สร้างอยู่ในลำดับที่ถูกต้อง


1
ฉันคิดว่าลิงค์ต่อไปนี้อธิบายถึงแนวทางที่ดี: egalluzzo.blogspot.co.at/2010/06/…
stuXnet

1
แต่คุณจะbuild()เอาท์พุทb.GMO(true).calories(100)อย่างไร?
Sridhar Sarnobat

คำตอบ:


170

คุณสามารถแก้ได้โดยใช้ generics ฉันคิดว่าสิ่งนี้เรียกว่า"รูปแบบทั่วไปที่เกิดขึ้นซ้ำซาก"

ทำให้ชนิดการส่งคืนของเมธอด base class builder เป็นอาร์กิวเมนต์ทั่วไป

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

ตอนนี้สร้างอินสแตนซ์ตัวสร้างฐานด้วยตัวสร้างคลาสที่ได้รับมาเป็นอาร์กิวเมนต์ทั่วไป

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
อืมฉันคิดว่าฉันจะต้อง (ก) โพสต์คำถามใหม่ (b) ออกแบบใหม่implementsแทนextendsหรือ (c) ทิ้งทุกอย่างทิ้งไป ตอนนี้ฉันมีข้อผิดพลาดในการคอมไพล์แปลก ๆ ที่leafBuilder.leaf().leaf()และleafBuilder.mid().leaf()ตกลง แต่leafBuilder.leaf().mid().leaf()ล้มเหลว ...
Ken YN

11
@gkamal return (T) this;ส่งผลให้มีการunchecked or unsafe operationsเตือน นี่เป็นเรื่องที่หลีกเลี่ยงไม่ได้ใช่ไหม?
Dmitry Minkovsky

5
หากต้องการแก้ไขunchecked castคำเตือนโปรดดูวิธีแก้ปัญหาที่แนะนำในคำตอบอื่น ๆ : stackoverflow.com/a/34741836/3114959
Stepan Vavra

8
ทราบว่าBuilder<T extends Builder>เป็นจริงrawtype - Builder<T extends Builder<T>>นี้ควรจะเป็น
Boris the Spider

2
@ user2957378 the Builderfor GMOFactsต้องเป็นแบบทั่วไปBuilder<B extends Builder<B>> extends NutritionFacts.Builder<Builder>- และรูปแบบนี้สามารถดำเนินการต่อไปได้หลายระดับตามที่ต้องการ หากคุณประกาศตัวสร้างที่ไม่ใช่แบบทั่วไปคุณจะไม่สามารถขยายรูปแบบได้
Boris the Spider

44

สำหรับการบันทึกเพื่อกำจัดไฟล์

unchecked or unsafe operations คำเตือน

สำหรับreturn (T) this;ข้อความที่ @dimadima และ @Thomas N. พูดถึงวิธีแก้ปัญหาต่อไปนี้ใช้ในบางกรณี

สร้างabstractตัวสร้างที่ประกาศประเภททั่วไป ( T extends Builderในกรณีนี้) และประกาศprotected abstract T getThis()วิธีนามธรรมดังนี้:

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

อ้างอิงhttp://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205สำหรับรายละเอียดเพิ่มเติม


เหตุใดbuild()วิธีการคืนค่า NutrutionFacts จึงอยู่ที่นี่?
mvd

@mvd เพราะนี่คือคำตอบของคำถาม? ในประเภทย่อยคุณจะแทนที่มันเช่นpublic GMOFacts build() { return new GMOFacts(this); }
Stepan Vavra

ปัญหาเกิดขึ้นเมื่อเราต้องการเพิ่มลูกคนที่ 2 BuilderC extends BuilderBและBuilderB extends BuilderAเมื่อBuilderBใดที่ไม่ได้abstract
sosite

1
นี่ไม่ใช่คำตอบสำหรับคำถามเพราะคลาสพื้นฐานอาจไม่ใช่นามธรรม!
Roland

"สร้างนามธรรมให้เป็นตัวสร้างที่ประกาศประเภททั่วไป" - ถ้าฉันต้องการใช้ตัวสร้างนั้นโดยตรงล่ะ
เดซี่

21

วิธีนี้กำหนดให้คลาส non-leaf ทั้งหมดเป็นนามธรรมจากบล็อกโพสต์และคลาส Leaf ทั้งหมดจะต้องถือเป็นที่สิ้นสุด

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

จากนั้นคุณมีคลาสระดับกลางที่ขยายคลาสนี้และตัวสร้างและอื่น ๆ อีกมากมายเท่าที่คุณต้องการ:

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

และในที่สุดก็เป็นคลาสใบคอนกรีตที่สามารถเรียกใช้วิธีการสร้างทั้งหมดกับพ่อแม่ในลำดับใดก็ได้:

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

จากนั้นคุณสามารถเรียกใช้เมธอดในลำดับใดก็ได้จากคลาสใดก็ได้ในลำดับชั้น:

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

คุณรู้หรือไม่ว่าทำไมคลาสใบไม้ถึงต้องเป็นที่สิ้นสุด? ฉันต้องการให้คลาสคอนกรีตของฉันเป็นคลาสย่อย แต่ไม่พบวิธีที่จะทำให้คอมไพเลอร์เข้าใจประเภทของBมันมักจะกลายเป็นคลาสพื้นฐาน
David Ganster

สังเกตว่าคลาส Builder ใน LeafClass ไม่เป็นไปตาม<T extends SomeClass, B extends SomeClass.Builder<T,B>> extends SomeClassParent.Builder<T,B>รูปแบบเดียวกับที่คลาส SecondLevel ตัวกลางทำอย่างไรโดยจะประกาศชนิดเฉพาะแทน คุณไม่สามารถติดตั้งชั้นเรียนได้จนกว่าคุณจะเข้าสู่ลีฟโดยใช้ประเภทที่เฉพาะเจาะจง แต่เมื่อคุณทำแล้วคุณจะไม่สามารถขยายได้อีกต่อไปเนื่องจากคุณใช้ประเภทเฉพาะและได้ละทิ้งรูปแบบเทมเพลตที่เกิดขึ้นซ้ำ ๆ อย่างน่าสงสัย ลิงค์นี้อาจช่วยได้: angelikalanger.com/GenericsFAQ/FAQSections/…
Q23

7

คุณสามารถลบล้างcalories()เมธอดและปล่อยให้มันส่งคืนตัวสร้างส่วนขยาย สิ่งนี้คอมไพล์เนื่องจาก Java สนับสนุนชนิดการส่งคืนโควาเรียน

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

อ่าฉันไม่รู้ว่าฉันมาจากพื้นหลัง C ++ นั่นเป็นแนวทางที่มีประโยชน์สำหรับตัวอย่างเล็ก ๆ นี้ แต่ด้วยคลาสเต็มรูปแบบการทำซ้ำทุกวิธีจะกลายเป็นความเจ็บปวดและความเจ็บปวดที่เกิดจากข้อผิดพลาดได้ง่าย อย่างไรก็ตาม +1 เพื่อสอนสิ่งใหม่ ๆ ให้ฉัน!
Ken YN

สำหรับฉันดูเหมือนว่าสิ่งนี้ไม่ได้ช่วยแก้ปัญหาอะไรเลย เหตุผล (IMO) สำหรับการจัดคลาสย่อยพาเรนต์คือการนำเมธอดพาเรนต์กลับมาใช้ใหม่โดยไม่ลบล้าง หากคลาสเป็นเพียงค่าออบเจ็กต์ที่ไม่มีตรรกะจริงในเมธอด builder ยกเว้นการตั้งค่าแบบง่ายการเรียกเมธอดหลักในเมธอดการลบล้างจะมีค่าเพียงเล็กน้อยหรือไม่มีเลย
Developer Dude

คำตอบช่วยแก้ปัญหาที่อธิบายไว้ในคำถาม: รหัสที่ใช้ตัวสร้างจะรวบรวมด้วยคำสั่งทั้งสอง เนื่องจากวิธีหนึ่งรวบรวมและอีกวิธีหนึ่งไม่ได้ฉันจึงเดาว่าต้องมีค่าบางอย่าง
Flavio

3

นอกจากนี้ยังมีอีกวิธีหนึ่งในการสร้างคลาสตามBuilderรูปแบบซึ่งสอดคล้องกับ "ชอบองค์ประกอบมากกว่าการสืบทอด"

กำหนดอินเทอร์เฟซคลาสพาเรนต์Builderนั้นจะสืบทอด:

public interface FactsBuilder<T> {

    public T calories(int val);
}

การใช้งานNutritionFactsเกือบจะเหมือนกัน (ยกเว้นBuilderการใช้อินเทอร์เฟซ '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;
    }
}

Builderของชั้นเด็กควรขยายอินเตอร์เฟซเดียวกัน (ยกเว้นการดำเนินงานที่แตกต่างกันทั่วไป):

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

สังเกตว่าNutritionFacts.Builderเป็นช่องที่อยู่ภายในGMOFacts.Builder(เรียกว่าbaseBuilder) วิธีการใช้งานจากวิธีการFactsBuilderเรียกอินเทอร์เฟซbaseBuilderที่มีชื่อเดียวกัน:

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

นอกจากนี้ยังมีการเปลี่ยนแปลงครั้งใหญ่ในตัวสร้างของGMOFacts(Builder builder). การเรียกครั้งแรกในตัวสร้างไปยังตัวสร้างคลาสพาเรนต์ควรผ่านอย่างเหมาะสมNutritionFacts.Builder:

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

การใช้GMOFactsคลาสเต็มรูปแบบ:

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

ตัวอย่างเต็ม 3 ระดับของการสืบทอดตัวสร้างหลายตัวจะมีลักษณะดังนี้:

(สำหรับเวอร์ชันที่มีตัวสร้างสำเนาสำหรับตัวสร้างดูตัวอย่างที่สองด้านล่าง)

ระดับแรก - ผู้ปกครอง (อาจเป็นนามธรรม)

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

ระดับที่สอง

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

ระดับที่สาม

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

และตัวอย่างการใช้งาน

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


เวอร์ชันที่ยาวขึ้นเล็กน้อยที่มีตัวสร้างการคัดลอกสำหรับตัวสร้าง:

ระดับแรก - ผู้ปกครอง (อาจเป็นนามธรรม)

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

ระดับที่สอง

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

ระดับที่สาม

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

และตัวอย่างการใช้งาน

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

ถ้าคุณไม่อยากละสายตาไปที่วงเล็บมุมหรือสามหรือบางทีอาจจะไม่รู้สึกว่าคุณ ... อืม ... ฉันหมายถึง ... ไอ ... คนที่เหลือในทีมของคุณจะเข้าใจอย่างรวดเร็ว รูปแบบยาสามัญประจำตัวคุณสามารถทำได้:

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

สนับสนุนโดย

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

และประเภทหลัก:

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

ประเด็นสำคัญ:

  • ห่อหุ้มวัตถุในตัวสร้างเพื่อให้การสืบทอดป้องกันไม่ให้คุณตั้งค่าฟิลด์บนวัตถุที่อยู่ในประเภทหลัก
  • การเรียกใช้ซูเปอร์เพื่อให้แน่ใจว่าตรรกะ (ถ้ามี) ที่เพิ่มลงในเมธอดตัวสร้างประเภทซุปเปอร์จะยังคงอยู่ในประเภทย่อย
  • ด้านล่างคือการสร้างวัตถุปลอมในคลาสพาเรนต์ ... แต่ดูวิธีทำความสะอาดด้านล่าง
  • Up side นั้นง่ายต่อการเข้าใจอย่างรวดเร็วและไม่มีคุณสมบัติการถ่ายโอนตัวสร้าง verbose
  • หากคุณมีหลายเธรดที่เข้าถึงออบเจ็กต์ตัวสร้างของคุณ ... ฉันคิดว่าฉันดีใจที่ไม่ใช่คุณ :)

แก้ไข:

ฉันพบวิธีการสร้างวัตถุปลอม ก่อนอื่นให้เพิ่มสิ่งนี้ให้กับผู้สร้างแต่ละคน:

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

จากนั้นในตัวสร้างสำหรับตัวสร้างแต่ละตัว:

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

ค่าใช้จ่ายเป็นไฟล์คลาสพิเศษสำหรับnew Object(){}คลาสภายในที่ไม่ระบุชื่อ


1

สิ่งหนึ่งที่คุณทำได้คือสร้างวิธีโรงงานแบบคงที่ในแต่ละชั้นเรียนของคุณ:

NutritionFacts.newBuilder()
GMOFacts.newBuilder()

วิธีการโรงงานแบบคงที่นี้จะส่งคืนตัวสร้างที่เหมาะสม คุณสามารถGMOFacts.Builderขยาย a NutritionFacts.Builderได้ซึ่งไม่ใช่ปัญหา ปัญหาที่นี่คือการจัดการกับการมองเห็น ...


0

การสนับสนุน IEEE ต่อไปนี้Refined Fluent Builder ใน Javaช่วยแก้ปัญหาได้อย่างครอบคลุม

มันแยกคำถามเดิมออกเป็นสองปัญหาย่อยของการขาดการถ่ายทอดทางพันธุกรรมและความไม่แปรเปลี่ยนเสมือนและแสดงให้เห็นว่าวิธีแก้ปัญหาย่อยทั้งสองนี้เปิดขึ้นสำหรับการสนับสนุนการสืบทอดด้วยการใช้โค้ดซ้ำในรูปแบบตัวสร้างแบบคลาสสิกใน Java


คำตอบนี้ไม่มีข้อมูลใด ๆ ที่จะเป็นประโยชน์ไม่มีอย่างน้อยสรุปคำตอบที่ให้ไว้ในลิงก์และนำไปสู่ลิงก์ที่ต้องเข้าสู่ระบบ
Sonata

คำตอบนี้เชื่อมโยงไปยังสิ่งพิมพ์สำหรับการประชุมที่ผ่านการตรวจสอบโดยเพื่อนซึ่งมีอำนาจเผยแพร่อย่างเป็นทางการและขั้นตอนการเผยแพร่และแบ่งปันอย่างเป็นทางการ
mc00x1

0

ฉันสร้างพาเรนต์คลาสตัวสร้างนามธรรมทั่วไปที่ยอมรับพารามิเตอร์ประเภททางการสองตัว อันดับแรกคือประเภทของอ็อบเจ็กต์ที่ส่งคืนโดย build () ส่วนที่สองคือประเภทที่ส่งคืนโดยตัวตั้งค่าพารามิเตอร์ทางเลือกแต่ละตัว ด้านล่างนี้คือชั้นเรียนผู้ปกครองและชั้นเรียนสำหรับเด็กเพื่อเป็นภาพประกอบ:

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

สิ่งนี้ได้ตอบสนองความต้องการของฉันเพื่อความพึงพอใจ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.