Perbaikan Pola Desain Pembangun Joshua Bloch?


12

Kembali pada tahun 2007, saya membaca sebuah artikel tentang Joshua Blochs mengambil "pola pembangun" dan bagaimana itu dapat dimodifikasi untuk meningkatkan penggunaan konstruktor dan setter yang berlebihan, terutama ketika sebuah objek memiliki sejumlah besar properti, yang sebagian besar bersifat opsional. Ringkasan singkat dari pola desain ini diartikulasikan di sini .

Saya menyukai ide itu, dan telah menggunakannya sejak saat itu. Masalahnya, meskipun sangat bersih dan menyenangkan untuk digunakan dari sudut pandang klien, menerapkannya bisa sangat menyebalkan! Ada begitu banyak tempat berbeda di objek tempat satu properti menjadi referensi, dan dengan demikian membuat objek, dan menambahkan properti baru membutuhkan banyak waktu.

Jadi ... saya punya ide. Pertama, contoh objek dengan gaya Joshua Bloch:

Josh Bloch Style:

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

Sekarang versi "peningkatan" saya:

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

    }
}

Seperti yang dapat Anda lihat di "versi yang ditingkatkan" saya, ada 2 tempat lebih sedikit di mana kami perlu menambahkan kode tentang properti tambahan (atau opsi, dalam hal ini)! Satu-satunya negatif yang dapat saya lihat adalah bahwa variabel instan dari kelas luar tidak dapat final. Tapi, kelas masih tetap tanpa ini.

Apakah benar-benar ada kerugian untuk perbaikan dalam pemeliharaan ini? Pasti ada alasan dia mengulangi properti di dalam kelas bersarang yang tidak aku lihat?


Ini tampaknya sangat mirip dengan saya mengambil pola pembangun di C # di sini .
MattDavey

Jawaban:


12

Variasi Anda cukup bagus. Tapi itu memungkinkan pengguna melakukan ini:

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!

Yang agak mengalahkan objek.

Anda dapat mengubah buildmetode untuk melakukan ini:

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

Yang akan mencegah ini - panggilan apa pun ke metode penyetel pada pembuat setelah buildpanggilan akan gagal dengan NullPointerException. Jika Anda ingin menjadi flash, Anda bahkan bisa menguji nol dan melempar IllegalStateException atau yang lainnya. Dan Anda bisa memindahkannya ke kelas dasar generik tempat itu bisa digunakan di semua pembangun.


1
Saya akan mengubah 2 baris dalam build()ke: this.options = new Options();. Dengan cara ini, instance Options akan berubah dengan aman, ditambah pembangun akan dapat digunakan kembali pada saat yang sama.
Natix

5

Pembangun dalam pola Bloch dapat digunakan berkali-kali untuk menghasilkan objek yang "sebagian besar" sama. Selain itu, objek yang tidak dapat diubah (semua bidang bersifat final dan sendiri tidak dapat diubah) memiliki keunggulan keamanan ulir yang dapat dikalahkan oleh perubahan Anda.


0

Jika Options secara efektif cloneable (maksud saya, terlepas dari antarmuka Cloneable), Anda dapat menggunakan pola prototipe - memilikinya di builder dan clone di build ().

Jika Anda tidak menggunakan antarmuka Cloneable, Anda harus menyalin setiap bidang, jadi itu akan menambah tempat lain di mana Anda perlu menambahkannya, jadi setidaknya untuk kelas dengan bidang sederhana yang sebenarnya menggunakan Cloneable akan menjadi ide yang baik.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.