Bagaimana saya harus menangani konfigurasi yang tidak kompatibel dengan pola Builder?

9

Ini dimotivasi oleh jawaban ini untuk pertanyaan yang terpisah .

The pola pembangun digunakan untuk menyederhanakan inisialisasi yang kompleks, terutama dengan parameter inisialisasi opsional). Tapi saya tidak tahu bagaimana mengelola dengan benar konfigurasi yang saling eksklusif.

Ini Imagekelasnya. Imagedapat diinisialisasi dari file atau dari ukuran, tetapi tidak keduanya . Menggunakan konstruktor untuk menegakkan pengecualian bersama ini jelas ketika kelas cukup sederhana:

public class Image
{
    public Image(Size size, Thing stuff, int range)
    {
    // ... initialize empty with size
    }

    public Image(string filename, Thing stuff, int range)
    {
        // ... initialize from file
    }
}

Sekarang anggaplah Image sebenarnya cukup dapat dikonfigurasi untuk pola builder menjadi berguna, tiba-tiba ini mungkin:

Image image = new ImageBuilder()
                  .setStuff(stuff)
                  .setRange(range)
                  .setSize(size)           // <----------  NOT
                  .setFilename(filename)   // <----------  COMPATIBLE
                  .build();

Masalah-masalah ini harus ditangkap pada saat dijalankan daripada pada waktu kompilasi, yang bukan hal terburuk. Masalahnya adalah bahwa secara konsisten dan komprehensif mendeteksi masalah ini di dalamImageBuilder kelas bisa menjadi kompleks, terutama dalam hal pemeliharaan.

Bagaimana saya harus menangani konfigurasi yang tidak kompatibel dalam pola builder?

kdbanman
sumber

Jawaban:

12

Anda mendapatkan Builder Anda. Namun, pada titik ini Anda memerlukan beberapa antarmuka.

Ada antarmuka FileBuilder yang mendefinisikan satu subset metode (tidak setSize) dan antarmuka SizeBuilder yang menentukan subset metode lain (tidaksetFilename ). Anda mungkin ingin memiliki antarmuka GenericBuilder memperluas FileBuilder dan SizeBuilder - itu tidak perlu meskipun beberapa orang mungkin lebih suka pendekatan itu.

Metode setSize()mengembalikan SizeBuilder. MetodesetFilename() mengembalikan FileBuilder.

ImageBuilder memiliki semua logika untuk keduanya setSize()dan setFileName(). Namun, tipe pengembalian untuk ini akan menentukan antarmuka subset yang sesuai.

class ImageBulder implements FileBuilder, SizeBuilder {
    ImageBuilder() {
        doInitThings;
    }

    ImageBuilder setStuff(Thing) {
        doStuff;
        return this;
    }

    ImageBuilder setRange(int range) {
        rangeStuff;
        return this;
    }

    SizeBuilder setSize(Size size) {
        stuff;
        return this;
    }

    FileBuilder setFilename(String filename) {
        otherStuff;
        return this;
    }

    Image build() {
        return new Image(...);
    }
}

Satu bit khusus di sini adalah bahwa sekali Anda memiliki SizeBuilder, semua pengembalian harus menjadi SizeBuilder. Tampilannya seperti:

interface SizeBuilder {
    SizeBuilder setRange(int range);
    SizeBuilder setSize(Size size);
    SizeBuilder setStuff(Thing stuff);
    Image build();
}

interface FileBuilder {
    FileBuilder setRange(int range);
    FileBuilder setFilename(String filename);
    FileBuilder setStuff(Thing stuff);
    Image build();
}

Dengan demikian, setelah Anda memanggil salah satu metode itu, Anda sekarang tidak dapat memanggil yang lain dan membuat objek dengan keadaan tidak valid.


sumber
Sangat menarik, terima kasih. Saya agak bingung bagaimana ini akan digunakan. Secara khusus, saya tidak tahu apa tipe deklarasi dan inisialisasi. Saya mungkin hanya membayangkan hal-hal yang jauh lebih rumit daripada yang diperlukan. Bisakah Anda memasukkan contoh penggunaan?
kdbanman
Pembangun gambar mengembalikan antarmuka yang sesuai dengan perubahan status yang dipanggil metode itu. Namun, setelah Anda mendapatkan kembali antarmuka tertentu dari ImageBuilder, panggilan mendatang terhadap objek tersebut dilakukan pada antarmuka yang membatasi kemampuan untuk memanggil metode yang tidak kompatibel.
1
@rwong sementara saya akan mengakui untuk tidak melihat terlalu dalam, masalah yang saya pikir saya miliki dengan pendekatan seperti itu adalah bahwa 'keadaan' pembangun bisa diatur ulang. Orang perlu memastikan bahwa setelah setSize () dipanggil, semua doa builder selanjutnya ada di SizeBuilder. Jika jenis setRange () bukan SizeBuilder atau sesuatu yang meluas / mengimplementasikan yang bisa digunakan untuk memanggil setFilename di atasnya lagi. Anda juga memiliki situasi (tidak dijelaskan di sini) di mana alih-alih ukuran Anda memiliki lebar int dan tinggi int sehingga keduanya perlu dipanggil.
1
@MichaelT Mengingat masalah pengelakan yang rumit, saya menduga bahwa menerapkan urutan ketat inisialisasi parameter (menghasilkan pohon awalan item parameter) mungkin merupakan hal yang baik ketika menggunakan pola builder. Akibatnya, item parameter umum seperti Rangedan Stuffharus diinisialisasi pada awalnya, bukan pada waktu yang sewenang-wenang.
rwong
1
@MichaelT: pada saat itu, LSP ikut bermain. Anda dapat yakin metode dari tipe semu ( RangeAndStuffBuilder) dapat dipanggil pada tipe aktual. Pembatasan lebih lanjut dapat diimplementasikan dengan mengembalikan lebih banyak tipe dasar untuk beberapa metode (meskipun ini akan menyebabkan peningkatan tipe yang eksponensial), yang secara efektif menghilangkan operasi. Selama hasil metode tidak kembali ke hierarki, Anda tidak akan mendapatkan kesalahan ketik. The setHeight/ setWidthSkenario dapat diterapkan dengan hirarki saudara yang tidak memiliki buildmetode.
outis