รูปแบบการสร้างใน Java ที่มีประสิทธิภาพ


137

ฉันเพิ่งเริ่มอ่าน Java ที่มีประสิทธิภาพโดย Joshua Bloch ฉันพบแนวคิดของรูปแบบของตัวสร้าง [รายการ 2 ในหนังสือ] ที่น่าสนใจจริงๆ ฉันพยายามใช้มันในโครงการของฉัน แต่มีข้อผิดพลาดในการรวบรวม ต่อไปนี้เป็นสิ่งสำคัญที่ฉันพยายามทำ:

คลาสที่มีหลายแอ็ตทริบิวต์และคลาสตัวสร้าง:

public class NutritionalFacts {
    private int sodium;
    private int fat;
    private int carbo;

    public class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder(int s) {
            this.sodium = s;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

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

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

คลาสที่ฉันพยายามใช้คลาสด้านบน:

public class Main {
    public static void main(String args[]) {
        NutritionalFacts n = 
            new NutritionalFacts.Builder(10).carbo(23).fat(1).build();
    }
}

ฉันได้รับข้อผิดพลาดของคอมไพเลอร์ต่อไปนี้:

อินสแตนซ์การปิดล้อมที่มี effectivejava.BuilderPattern.Nut โภชนาการFacts.Builderจำเป็นต้องใช้ NutritionFacts n = Nut NutFactsBuilder ใหม่ (10) .carbo (23) .Fat (1). build ();

ฉันไม่เข้าใจความหมายของข้อความ กรุณาอธิบาย. โค้ดด้านบนคล้ายกับตัวอย่างที่ Bloch แนะนำในหนังสือของเขา


คำตอบ:


171

ทำให้ผู้สร้างเป็นstaticคลาส จากนั้นมันจะทำงาน ถ้ามันไม่คงที่มันจะต้องมีอินสแตนซ์ของคลาสที่เป็นเจ้าของ - และจุดนั้นไม่ควรมีอินสแตนซ์ของมัน

public class NutritionFacts {
    public static class Builder {
    }
}

การอ้างอิง: คลาสที่ซ้อนกัน


34
และในความเป็นจริงนั้นBuilderเป็นstaticตัวอย่างในหนังสือเล่มนี้ (หน้า 14, บรรทัดที่ 10 ในรุ่นที่ 2)
Powerlord

27

คุณควรทำให้คลาส Builder เป็นแบบสแตติกและคุณควรทำให้ฟิลด์สุดท้ายและมี getters เพื่อรับค่าเหล่านั้น ไม่ได้ให้ตัวตั้งค่าเหล่านั้น ด้วยวิธีนี้ชั้นเรียนของคุณจะไม่เปลี่ยนแปลงอย่างสมบูรณ์แบบ

public class NutritionalFacts {
    private final int sodium;
    private final int fat;
    private final int carbo;

    public int getSodium(){
        return sodium;
    }

    public int getFat(){
        return fat;
    }

    public int getCarbo(){
        return carbo;
    }

    public static class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder sodium(int s) {
            this.sodium = s;
            return this;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

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

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

และตอนนี้คุณสามารถตั้งค่าคุณสมบัติได้ดังนี้:

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();

ทำไมไม่เพียงทำให้ฟิลด์ Nut NutFacts เป็นสาธารณะ พวกมันสุดท้ายแล้วและมันก็ยังคงไม่เปลี่ยนรูป
skia.heliou

finalเขตข้อมูลทำให้รู้สึกเฉพาะในกรณีที่จำเป็นต้องมีเขตข้อมูลระหว่างการเริ่มต้น ถ้าไม่เช่นนั้นก็ไม่ควรเป็นfinalเช่นนั้น
Piotrek Hryciuk

12

คุณกำลังพยายามเข้าถึงคลาสที่ไม่คงที่ในทางคงที่ เปลี่ยนBuilderเป็นstatic class Builderและควรใช้งานได้

ตัวอย่างการใช้งานที่คุณให้ล้มเหลวเนื่องจากไม่มีอินสแตนซ์ในBuilderปัจจุบัน คลาสแบบสแตติกสำหรับวัตถุประสงค์ในทางปฏิบัติทั้งหมดจะถูกสร้างอินสแตนซ์เสมอ หากคุณไม่ทำให้มันคงที่คุณจะต้องพูดว่า:

Widget = new Widget.Builder(10).setparm1(1).setparm2(3).build();

เพราะคุณจะต้องสร้างใหม่Builderทุกครั้ง


12

หากต้องการสร้างเครื่องมือสร้างภายในใน Intellij IDEA ให้ตรวจสอบปลั๊กอินนี้: https://github.com/analytically/innerbuilder


2
สิ่งนี้ไม่เกี่ยวข้องกับคำถามที่ถาม แต่มีประโยชน์มาก! ยินดีที่ได้พบ!
Androider หิว

8

คุณจำเป็นต้องประกาศระดับชั้นเป็นBuilderstatic

ศึกษาเอกสารบางอย่างสำหรับทั้งคลาสภายในที่ไม่คงที่และคลาสภายในคงที่การเรียนภายในแบบคงที่

โดยทั่วไปอินสแตนซ์ของคลาสภายในที่ไม่คงที่ไม่สามารถมีอยู่ได้


5

เมื่อคุณมีความคิดในทางปฏิบัติคุณอาจพบว่าลอมบอก@Builderสะดวกกว่ามาก

@Builder ให้คุณสร้างรหัสที่จำเป็นโดยอัตโนมัติเพื่อให้ชั้นเรียนของคุณสามารถใช้งานรหัสได้ทันทีเช่น:

Person.builder()
  .name("Adam Savage")
  .city("San Francisco")
  .job("Mythbusters")
  .job("Unchained Reaction")
 .build(); 

เอกสารอย่างเป็นทางการ: https://www.projectlombok.org/features/Builder


4

หมายความว่าคุณไม่สามารถสร้างประเภทที่แนบมา นี่หมายความว่าก่อนอื่นคุณต้องสร้างอินสแตนซ์ของคลาส "พาเรนต์" และจากอินสแตนซ์นี้คุณสามารถสร้างอินสแตนซ์ของคลาสที่ซ้อนกันได้

NutritionalFacts n = new NutritionalFacts()

Builder b = new n.Builder(10).carbo(23).fat(1).build();

คลาสที่ซ้อนกัน


3
ที่ไม่สมเหตุสมผลเพราะเขาต้องการผู้สร้างเพื่อสร้าง "ข้อเท็จจริง" ไม่ใช่วิธีอื่น ๆ
Bozho

5
จริงถ้าเรามุ่งเน้นที่รูปแบบการสร้างฉันมุ่งเน้นเฉพาะใน "ฉันไม่เข้าใจความหมายของข้อความ" และนำเสนอหนึ่งในสองวิธี
Damian Leszczyński -

3

คลาส Builder ควรเป็นแบบสแตติก ตอนนี้ฉันไม่มีเวลาที่จะทดสอบโค้ดจริง ๆ แล้ว แต่ถ้ามันไม่ทำงานให้ฉันรู้แล้วฉันจะลองดูอีกครั้ง


1

โดยส่วนตัวฉันชอบที่จะใช้วิธีอื่นเมื่อคุณมี 2 คลาสที่แตกต่าง ดังนั้นคุณไม่ต้องการคลาสแบบคงที่ใด ๆ นี่เป็นพื้นเพื่อหลีกเลี่ยงการเขียนClass.Builderเมื่อคุณต้องสร้างอินสแตนซ์ใหม่

public class Person {
    private String attr1;
    private String attr2;
    private String attr3;

    // package access
    Person(PersonBuilder builder) {
        this.attr1 = builder.getAttr1();
        // ...
    }

    // ...
    // getters and setters 
}

public class PersonBuilder (
    private String attr1;
    private String attr2;
    private String attr3;

    // constructor with required attribute
    public PersonBuilder(String attr1) {
        this.attr1 = attr1;
    }

    public PersonBuilder setAttr2(String attr2) {
        this.attr2 = attr2;
        return this;
    }

    public PersonBuilder setAttr3(String attr3) {
        this.attr3 = attr3;
        return this;
    }

    public Person build() {
        return new Person(this);
    }
    // ....
}

ดังนั้นคุณสามารถใช้เครื่องมือสร้างของคุณเช่นนี้:

Person person = new PersonBuilder("attr1")
                            .setAttr2("attr2")
                            .build();

0

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

พิจารณาสิ่งนี้. การนำเครื่องมือสร้างมาใช้โดยการประกาศตัวติดตั้งwithProperty(value)ชนิดภายในคลาสและทำให้พวกมันคืนค่าการอ้างอิงกลับไปเอง ในวิธีการนี้คุณจะมีชั้นเรียนเดี่ยวและสง่างามซึ่งเป็นหัวข้อที่ปลอดภัยและรัดกุม

พิจารณาสิ่งนี้:

public class DataObject {

    private String first;
    private String second;
    private String third;

    public String getFirst(){
       return first; 
    }

    public void setFirst(String first){
       this.first = first; 
    }

    ... 

    public DataObject withFirst(String first){
       this.first = first;
       return this; 
    }

    public DataObject withSecond(String second){
       this.second = second;
       return this; 
    }

    public DataObject withThird(String third){
       this.third = third;
       return this; 
    }
}


DataObject dataObject = new DataObject()
     .withFirst("first data")
     .withSecond("second data")
     .withThird("third data");

ลองดูตัวอย่างเพิ่มเติมของJava Builder


0

คุณจำเป็นต้องเปลี่ยนสร้างชั้นเรียนเพื่อสร้างระดับคงที่ จากนั้นมันก็จะทำงานได้ดี

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