Tại sao mô hình xây dựng thường được thực hiện như thế này?


8

Thường thì tôi thấy việc triển khai mẫu trình xây dựng (trong Java) là như thế này:

public class Foo {
    private Foo(FooBuilder builder) {
        // get alle the parameters from the builder and apply them to this instance
    }

    public static class FooBuilder {
        // ...
        public Foo build() {
            return new Foo(this); // <- this part is what irritates me
        }
    }
}

Ví dụ cũng ở đây:

Tại sao mọi người giới thiệu sự phụ thuộc mạnh mẽ (lẫn nhau) giữa người xây dựng và loại để xây dựng?

EDIT: Ý tôi là, tôi biết rằng về bản chất, trình xây dựng được gắn với lớp tôi muốn xây dựng một thể hiện từ ( FooBuilder -> Foo). Điều tôi thắc mắc là: tại sao lại có sự cần thiết cho cách khác ( Foo -> FooBuilder)

Ngược lại, tôi thấy các triển khai (ít thường xuyên hơn) tạo ra một bản năng mới Fookhi trình tạo được tạo và đặt các trường theo trường hợp Fookhi các phương thức xây dựng thích hợp được gọi. Tôi thích cách tiếp cận này tốt hơn, bởi vì nó vẫn có API thông thạo và không ràng buộc lớp với trình tạo của nó. Có bất kỳ nhược điểm của phương pháp khác này?


Câu hỏi của bạn cũng áp dụng nếu nhà Fooxây dựng là công khai?
Phát hiện

1
Lý do là KHÔ. Nếu bạn có nhiều trường, chúng sẽ phải được lặp lại trong hàm tạo của Foo nếu Foo không biết về FooBuilder. Bằng cách này, bạn lưu các nhà xây dựng của Foo.
Esben Skov Pedersen

Câu trả lời:


8

Nó sôi sục đến mức không bao giờ có Foo trong trạng thái được xây dựng một nửa

Một số lý do mà mùa xuân đến tâm trí:

Bản chất của người xây dựng gắn liền với lớp mà nó đang xây dựng. Bạn có thể nghĩ về các trạng thái trung gian của trình xây dựng là ứng dụng một phần của hàm tạo, do đó, lập luận của bạn rằng nó quá khớp nối không có sức thuyết phục.

Bằng cách chuyển toàn bộ trình xây dựng vào, các trường trong MyClass có thể là cuối cùng, làm cho lý luận về MyClass dễ dàng hơn. Nếu các trường không phải là cuối cùng, bạn có thể dễ dàng có setters thông thạo hơn so với một người xây dựng.


1. về bản chất, trình xây dựng được gắn với lớp mà tôi muốn xây dựng một thể hiện từ (FooBuilder -> Foo). Điều tôi thắc mắc là: tại sao lại có sự cần thiết cho cách khác (Foo -> FooBuilder) 2. Tôi thấy điểm với công cụ sửa đổi cuối cùng , nhưng bên cạnh công cụ sửa đổi đó, FooBuilder có thể chỉ cần đặt các trường của Foo (ngay cả khi họ là riêng tư, bởi vì FooBuilder là một phần của Foo) và nếu tôi không tạo setters, thì trường hợp này vẫn không thay đổi, phải không?
Andy

Nó sôi sục khi không bao giờ có Foo ở trạng thái được xây dựng một nửa, ngay cả khi nó chỉ là nội bộ đối với người xây dựng. Bạn có nghĩ rằng một vấn đề là trong một lớp có một tập hợp các hàm tạo quá tải lớn, có một "sự phụ thuộc mạnh mẽ (lẫn nhau" giữa các hàm tạo và phần còn lại của lớp không? Bởi vì đó là những gì một Builder là - một cách ngắn gọn để thể hiện rất nhiều các nhà xây dựng tương tự
Caleth

Cảm ơn câu trả lời (về việc không bao giờ có trạng thái không nhất quán), điều đó làm cho nó rõ ràng. Tôi không thích câu trả lời nhưng giờ tôi đã hiểu. Khi bạn kết hợp điều đó trong bạn trả lời, tôi sẽ chấp nhận nó.
Andy

3

Trình xây dựng sẽ giữ các trường có thể thay đổi, được đặt bằng các phương thức. Lớp thực tế ( Foo) sau đó có thể tạo finalcác trường bất biến từ trình xây dựng.

Vì vậy, Builder là một lớp trạng thái.

Nhưng người xây dựng có thể gọi là alse new Foo(x, y, z). Đó sẽ là IMHO một chiều, phong cách tốt hơn. Khi đó, mô hình chống, trường FooBuilder hoặc như vậy sẽ không thể thực hiện được. Mặt khác, FooBuilder đảm bảo tính toàn vẹn của dữ liệu.

Mặt khác, FooBuilder đóng hai vai trò: xây dựng với API thông thạo và để trình bày dữ liệu cho nhà xây dựng. Nó là một lớp kiểu sự kiện cho hàm tạo. Trong một mã được thiết kế quá mức, các tham số hàm tạo có thể được giữ trong một số FooConstructorParams.


2

Bởi vì "Builder" thường được sử dụng để giải quyết "vấn đề nhà xây dựng kính thiên văn", đặc biệt là trong bối cảnh các lớp không thay đổi. Xem liên kết này cho một ví dụ đầy đủ.

Hãy xem xét các lựa chọn thay thế sẽ như thế nào:

 public static class FooBuilder {
    // works with immutable classes, but leads to telescoping constructor
    public Foo build() {
        return new Foo(par1, par2, par3, ....); 
    }
 }

Hoặc là:

 public static class FooBuilder {
    // does not work for immutable classes:
    public Foo build() {
        var foo = new Foo(); 
        foo.Attribute1=par1;
        foo.Attribute2=par2;
        //...
        return foo;
    }
 }

Vì vậy, đặc biệt đối với các lớp không thay đổi, nếu bạn muốn không có hàm tạo với một loạt các tham số, thực sự không có thay thế nào.


Tôi biết những gì vấn đề mô hình xây dựng giải quyết. Nhưng bạn đã không trả lời câu hỏi của tôi tại sao việc triển khai thường giống như ví dụ được đưa ra. Ngay cả Josh Bloch trong bài viết bạn liên kết cũng không. Tuy nhiên, Caleth làm trong bình luận của anh ấy / cô ấy.
Andy

@Andy: Tôi đã đưa ra một ví dụ để làm rõ lý do tại sao điều này trả lời câu hỏi của bạn.
Doc Brown

Trên thực tế, ví dụ thứ hai của bạn không nhất thiết phải thay đổi. Nếu bạn không thực hiện các phương thức setter thì không thể thay đổi được . Nhưng bây giờ tôi thấy, nơi bạn đang hướng tới.
Andy

@Andy: Tôi thực sự có thể viết lại ví dụ bằng setters nếu bạn thích thành ngữ Java thay vì thành ngữ C #. Nhưng nếu foo.Attribute1=par1có thể, Foochắc chắn là có thể thay đổi, ngay cả trong Java. Các lớp bất biến cần phải cấm bất kỳ sự đột biến nào về trạng thái của chúng sau khi xây dựng, không phải bởi setters, cũng không phải bằng các phương thức làm thay đổi trạng thái của chúng, cũng như bằng cách phơi bày các thuộc tính thành viên công cộng.
Doc Brown

"Vì vậy, đặc biệt đối với các lớp bất biến" .... Nhưng một lớp không có setters vẫn sẽ xuất hiện bất biến từ bên ngoài. Cho dù họ là cuối cùng hay không là một tốt đẹp để có.
Esben Skov Pedersen
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.