Mengapa pola pembangun sering diterapkan seperti ini?

8

Seringkali saya melihat implementasi pola builder (di Jawa) menjadi seperti ini:

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

Contoh juga di sini:

Mengapa orang memperkenalkan ketergantungan (saling) kuat antara pembangun dan tipe untuk membangun?

EDIT: Maksud saya, saya tahu bahwa pada dasarnya pembangun terkait dengan kelas yang ingin saya bangun instance dari ( FooBuilder -> Foo). Yang saya tanyakan adalah: mengapa ada kebutuhan untuk sebaliknya ( Foo -> FooBuilder)

Sebaliknya saya melihat (lebih jarang) implementasi yang membuat instace baru Fooketika builder dibuat dan mengatur bidang pada instance Fooketika metode builder yang sesuai dipanggil. Saya suka pendekatan ini lebih baik, karena masih memiliki API yang lancar dan tidak mengikat kelas untuk pembangunnya. Apakah ada kelemahan dari pendekatan lain ini?

Andy
sumber
Apakah pertanyaan Anda juga berlaku jika Fookonstruktor bersifat publik?
Melihat
1
Alasannya KERING. Jika Anda memiliki banyak bidang, mereka harus diulang dalam konstruktor Foo jika Foo tidak tahu tentang FooBuilder. Dengan cara ini Anda menyimpan konstruktor Foo.
Esben Skov Pedersen

Jawaban:

8

Itu bermuara untuk tidak pernah memiliki Foo dalam keadaan setengah dibangun

Beberapa alasan yang muncul dalam pikiran:

Sang pembangun pada dasarnya terikat pada kelas yang dibangunnya. Anda dapat menganggap status perantara dari pembangun sebagai aplikasi sebagian konstruktor, sehingga argumen Anda bahwa itu terlalu berpasangan tidak persuasif.

Dengan melewati seluruh builder, bidang di MyClass bisa final, membuat penalaran tentang MyClass lebih mudah. Jika bidang tidak final, Anda bisa dengan mudah memiliki setter lancar dari pembangun.

Caleth
sumber
1. pada dasarnya pembangun terikat ke kelas saya ingin membangun sebuah instance dari (FooBuilder -> Foo). Yang saya tanyakan adalah: mengapa ada keharusan untuk sebaliknya (Foo -> FooBuilder) 2. Saya melihat titik dengan pengubah akhir , tetapi selain pengubah itu, FooBuilder dapat dengan mudah mengatur bidang Foo (bahkan jika mereka bersifat pribadi, karena FooBuilder adalah bagian dari Foo), dan jika saya tidak membuat setter, contohnya masih tidak dapat diubah, bukan?
Andy
Itu bermuara pada tidak pernah memiliki Foo dalam keadaan setengah dibangun, bahkan jika itu hanya internal untuk pembangun. Apakah Anda pikir itu masalah bahwa di kelas dengan set konstruktor yang kelebihan beban besar, ada "ketergantungan (saling) yang kuat" antara konstruktor dan seluruh kelas? Karena itulah yang disebut Builder - cara ringkas untuk mengekspresikan banyak konstruktor serupa
Caleth
Terima kasih atas jawabannya (tentang tidak pernah memiliki keadaan tidak konsisten), yang membuatnya jelas. Saya tidak suka jawabannya tetapi saya mengerti sekarang. Ketika Anda memasukkan itu dalam jawaban Anda, saya akan menerimanya.
Andy
3

Pembangun akan memegang bidang yang bisa berubah, diatur dengan metode. Kelas aktual ( Foo) kemudian dapat membuat finalbidang tidak berubah dari pembangun.

Jadi Builder adalah kelas stateful.

Tapi pembangun lain bisa saja menelepon new Foo(x, y, z). Itu akan lebih satu arah, IMHO gaya yang lebih baik. Saat anti-pola, bidang FooBuilder atau semacamnya tidak akan mungkin. Di sisi lain, FooBuilder menjamin integritas data.

Di sisi lain, FooBuilder memainkan dua peran: untuk membangun dengan API yang lancar, dan untuk menyajikan data ke konstruktor. Ini adalah kelas tipe acara untuk konstruktor. Dalam kode overdesigned params konstruktor dapat disimpan di beberapa FooConstructorParams.

Joop Eggen
sumber
2

Karena "Builder" biasanya digunakan untuk memecahkan "masalah konstruktor teleskop", terutama dalam konteks kelas yang tidak dapat diubah. Lihat tautan ini untuk contoh lengkap.

Pertimbangkan seperti apa alternatifnya:

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

Atau:

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

Jadi terutama untuk kelas yang tidak dapat diubah, jika Anda tidak menginginkan konstruktor dengan banyak parameter, sebenarnya tidak ada alternatif.

Doc Brown
sumber
Saya tahu masalah apa yang dipecahkan oleh pola pembangun. Tetapi Anda tidak menjawab pertanyaan saya mengapa implementasinya sering terlihat seperti contoh yang diberikan. Bahkan Josh Bloch dalam artikel yang Anda tautkan tidak. Namun, Caleth tidak dalam komentarnya.
Andy
@Andy: Saya memberi contoh untuk menjelaskan mengapa ini menjawab pertanyaan Anda.
Doc Brown
Sebenarnya, contoh kedua Anda belum tentu bisa berubah. Jika Anda tidak menerapkan metode setter, metode ini tidak dapat diubah . Tapi sekarang saya tahu, ke mana Anda pergi.
Andy
@Andy: Saya sebenarnya bisa menulis ulang contoh menggunakan setter jika Anda lebih suka idiom Java daripada idiom C #. Tetapi jika foo.Attribute1=par1memungkinkan, Foopasti bisa berubah, bahkan di Jawa. Kelas yang tidak dapat berubah perlu untuk melarang mutasi negara mereka setelah konstruksi, baik dengan setter, atau dengan metode mutasi negara mereka, atau dengan mengekspos atribut anggota publik.
Doc Brown
"Jadi terutama untuk kelas yang tidak berubah" .... Tapi kelas tanpa setter masih akan tampak tidak berubah dari luar. Apakah mereka final atau tidak, adalah barang yang bagus untuk dimiliki.
Esben Skov Pedersen