Những cải tiến cho Mẫu thiết kế Builder của Joshua Bloch?


12

Quay trở lại năm 2007, tôi đã đọc một bài viết về Joshua Blochs về "mẫu xây dựng" và cách nó có thể được sửa đổi để cải thiện việc sử dụng quá mức các hàm tạo và setters, đặc biệt là khi một đối tượng có số lượng lớn các thuộc tính, hầu hết là tùy chọn. Một bản tóm tắt ngắn gọn về mẫu thiết kế này được phát biểu ở đây .

Tôi thích ý tưởng này, và đã sử dụng nó từ đó. Vấn đề với nó, trong khi nó rất sạch sẽ và tốt đẹp để sử dụng từ góc độ khách hàng, thực hiện nó có thể là một nỗi đau trong ăn mày! Có rất nhiều vị trí khác nhau trong đối tượng trong đó một thuộc tính là tham chiếu và do đó tạo đối tượng và thêm một thuộc tính mới mất rất nhiều thời gian.

Vì vậy, ... tôi đã có một ý tưởng. Đầu tiên, một đối tượng ví dụ theo phong cách của Joshua Bloch:

Phong cách Josh Bloch:

public class OptionsJoshBlochStyle {

    private final String option1;
    private final int option2;
    // ...other options here  <<<<

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private String option1;
        private int option2;
        // other options here <<<<<

        public Builder option1(String option1) {
            this.option1 = option1;
            return this;
        }

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

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

    private OptionsJoshBlochStyle(Builder builder) {
        this.option1 = builder.option1;
        this.option2 = builder.option2;
        // other options here <<<<<<
    }

    public static void main(String[] args) {
        OptionsJoshBlochStyle optionsVariation1 = new OptionsJoshBlochStyle.Builder().option1("firefox").option2(1).build();
        OptionsJoshBlochStyle optionsVariation2 = new OptionsJoshBlochStyle.Builder().option1("chrome").option2(2).build();
    }
}

Bây giờ phiên bản "cải tiến" của tôi:

public class Options {

    // note that these are not final
    private String option1;
    private int option2;
    // ...other options here

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private final Options options = new Options();

        public Builder option1(String option1) {
            this.options.option1 = option1;
            return this;
        }

        public Builder option2(int option2) {
            this.options.option2 = option2;
            return this;
        }

        public Options build() {
            return options;
        }
    }

    private Options() {
    }

    public static void main(String[] args) {
        Options optionsVariation1 = new Options.Builder().option1("firefox").option2(1).build();
        Options optionsVariation2 = new Options.Builder().option1("chrome").option2(2).build();

    }
}

Như bạn có thể thấy trong "phiên bản cải tiến" của tôi, có 2 vị trí ít hơn trong đó chúng tôi cần thêm mã về bất kỳ thuộc tính bổ sung nào (hoặc tùy chọn, trong trường hợp này)! Điểm trừ duy nhất mà tôi có thể thấy là các biến thể hiện của lớp bên ngoài không thể là cuối cùng. Nhưng, lớp học vẫn bất biến nếu không có điều này.

Có thực sự có bất kỳ nhược điểm để cải thiện khả năng bảo trì này? Có phải có một lý do mà anh ta lặp lại các thuộc tính trong lớp lồng mà tôi không thấy?


Điều này có vẻ rất giống với việc tôi lấy mẫu xây dựng trong C # ở đây .
MattDavey

Câu trả lời:


12

Biến thể của bạn là khá tốt đẹp. Nhưng nó cho phép người dùng làm điều này:

Options.Builder builder = new Options.Builder().option1("firefox").option2(1);
Options optionsVariation1 = builder.build();
assert optionsVariation1.getOption1().equals("firefox");
builder.option1("chrome");
assert optionsVariation1.getOption1().equals("firefox"); // FAILURE!

Mà đánh bại đối tượng.

Bạn có thể thay đổi buildphương thức để làm điều này:

public Options build() {
    Options options = this.options;
    this.options = null;
    return options;
}

Điều này sẽ ngăn chặn điều này - bất kỳ cuộc gọi nào đến một phương thức setter trên trình xây dựng sau khi buildcuộc gọi sẽ thất bại với NullPulumException. Nếu bạn muốn flash, thậm chí bạn có thể kiểm tra null và ném IllegalStateException hoặc một cái gì đó thay thế. Và bạn có thể chuyển nó lên một lớp cơ sở chung nơi nó có thể được sử dụng trên tất cả các trình xây dựng.


1
Tôi sẽ thay đổi dòng thứ 2 build()thành : this.options = new Options();. Bằng cách này, các phiên bản Tùy chọn sẽ không thay đổi một cách an toàn, cộng với trình tạo sẽ có thể được sử dụng lại cùng một lúc.
Natix

5

Trình xây dựng trong mẫu của Bloch có thể được sử dụng nhiều lần để tạo các đối tượng "hầu hết" giống nhau. Ngoài ra, các đối tượng bất biến (tất cả các trường là cuối cùng và chính chúng là bất biến) có các ưu điểm an toàn luồng mà các thay đổi của bạn có thể đánh bại.


0

Nếu Tùy chọn có thể sao chép một cách hiệu quả (ý tôi là, bất kể giao diện Clonizable), bạn có thể sử dụng mẫu nguyên mẫu - có một mẫu trong trình tạo và sao chép nó trong build ().

Nếu bạn không sử dụng giao diện Clonizable, bạn sẽ phải sao chép mọi trường, vì vậy nó sẽ thêm một nơi khác mà bạn cần thêm nó, vì vậy, ít nhất là đối với lớp có các trường đơn giản sử dụng Clonizable sẽ là ý 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.