Trình tạo mẫu trong Java hiệu quả


137

Gần đây tôi đã bắt đầu đọc Java hiệu quả của Joshua Bloch. Tôi thấy ý tưởng về mẫu Builder [Mục 2 trong cuốn sách] thực sự thú vị. Tôi đã cố gắng thực hiện nó trong dự án của tôi nhưng có lỗi biên dịch. Sau đây là bản chất những gì tôi đã cố gắng làm:

Lớp có nhiều thuộc tính và lớp trình tạo của nó:

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

Lớp nơi tôi cố gắng sử dụng lớp trên:

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

Tôi nhận được lỗi trình biên dịch sau:

một ví dụ kèm theo có chứa hiệu quả.BuilderPotype.NutologistsFacts.Builder là bắt buộc về dinh dưỡng

Tôi không hiểu ý nghĩa của tin nhắn. Vui lòng giải thích. Đoạn mã trên giống với ví dụ được đề xuất bởi Bloch trong cuốn sách của mình.


Câu trả lời:


171

Làm cho người xây dựng một staticlớp. Sau đó, nó sẽ làm việc. Nếu nó không tĩnh, nó sẽ yêu cầu một thể hiện của lớp sở hữu của nó - và vấn đề là không có một thể hiện của nó, và thậm chí cấm tạo các thể hiện mà không có trình xây dựng.

public class NutritionFacts {
    public static class Builder {
    }
}

Tham khảo: Các lớp lồng nhau


34
Và, trên thực tế, Builderstatictrong ví dụ trong cuốn sách (trang 14, dòng 10 trong Ấn bản 2).
Powerlord

27

Bạn nên làm cho lớp Builder là tĩnh và bạn cũng nên làm cho các trường cuối cùng và có các getters để có được các giá trị đó. Đừng cung cấp setters cho những giá trị đó. Bằng cách này, lớp học của bạn sẽ hoàn toàn bất biến.

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

Và bây giờ bạn có thể thiết lập các thuộc tính như sau:

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

Tại sao không chỉ làm cho các lĩnh vực dinh dưỡng công khai? Họ đã là cuối cùng, và nó sẽ vẫn bất biến.
skia.heliou

finalcác trường chỉ có ý nghĩa nếu các trường luôn luôn cần thiết trong quá trình khởi tạo. Nếu không, thì các lĩnh vực không nên final.
Piotrek Hryciuk

12

Bạn đang cố gắng truy cập một lớp không tĩnh theo cách tĩnh. Thay đổi Builderđể static class Buildervà nó sẽ làm việc.

Việc sử dụng ví dụ bạn đưa ra không thành công vì không có trường hợp Builderhiện tại. Một lớp tĩnh cho tất cả các mục đích thực tế luôn luôn được khởi tạo. Nếu bạn không làm cho nó tĩnh, bạn cần nói:

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

Bởi vì bạn sẽ cần phải xây dựng một cái mới Buildermỗi lần.




5

Khi bạn đã có một ý tưởng, trong thực tế, bạn có thể thấy @Builderthuận tiện hơn nhiều.

@Builder cho phép bạn tự động tạo mã cần thiết để lớp của bạn có thể hiển thị với mã như:

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

Tài liệu chính thức: https://www.projectlombok.org/features/Builder


4

Điều này có nghĩa là bạn không thể tạo kiểu bao vây. Điều này có nghĩa là trước tiên bạn phải chọn một thể hiện của lớp "cha mẹ" và sau đó từ thể hiện này, bạn có thể tạo các thể hiện của lớp lồng nhau.

NutritionalFacts n = new NutritionalFacts()

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

Lớp học lồng nhau


3
điều đó không có ý nghĩa gì nhiều, bởi vì anh ta cần người xây dựng để xây dựng "sự thật", chứ không phải theo cách khác.
Bozho

5
đúng nếu chúng ta tập trung vào mẫu xây dựng, tôi chỉ tập trung vào "Tôi không hiểu ý nghĩa của thông điệp" và đã trình bày một trong hai giải pháp.
Damian Leszczyński - Vash

3

Lớp Builder nên tĩnh. Tôi không có thời gian ngay bây giờ để thực sự kiểm tra mã ngoài đó, nhưng nếu nó không hoạt động hãy cho tôi biết và tôi sẽ xem xét lại.


1

Cá nhân tôi thích sử dụng cách tiếp cận khác, khi bạn có 2 lớp khác nhau. Vì vậy, bạn không cần bất kỳ lớp tĩnh. Điều này về cơ bản là để tránh ghi Class.Builderkhi bạn phải tạo một thể hiện mới.

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

Vì vậy, bạn có thể sử dụng trình xây dựng của mình như thế này:

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

0

Như nhiều người đã nêu ở đây, bạn cần phải thực hiện lớp học static. Chỉ cần bổ sung nhỏ - nếu bạn muốn, có một cách khác một chút mà không cần tĩnh.

Xem xét điều này. Triển khai một trình xây dựng bằng cách khai báo một cái gì đó giống như withProperty(value)kiểu setters bên trong lớp và làm cho chúng trả về một tham chiếu đến chính nó. Trong phương pháp này, bạn có một lớp duy nhất và thanh lịch, đó là một chủ đề an toàn và súc tích.

Xem xét điều này:

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

Kiểm tra nó để biết thêm các ví dụ về Java Builder .


0

Bạn cần thay đổi lớp Builder thành Builder lớp tĩnh . Sau đó, nó sẽ hoạt động tốt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.