Pola Builder di Java yang Efektif

138

Saya baru-baru ini mulai membaca Java Efektif oleh Joshua Bloch. Saya menemukan ide tentang pola Builder [Item 2 dalam buku] sangat menarik. Saya mencoba menerapkannya dalam proyek saya tetapi ada kesalahan kompilasi. Berikut adalah intinya apa yang saya coba lakukan:

Kelas dengan banyak atribut dan kelas pembangunnya:

public class NutritionalFacts {
    private int sodium;
    private int fat;
    private int carbo;

    public class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder(int s) {
            this.sodium = s;
        }

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

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

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

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

Kelas tempat saya mencoba menggunakan kelas di atas:

public class Main {
    public static void main(String args[]) {
        NutritionalFacts n = 
            new NutritionalFacts.Builder(10).carbo(23).fat(1).build();
    }
}

Saya mendapatkan kesalahan kompiler berikut:

instans penutup yang berisi effectivejava.BuilderPattern.NutritionalFacts.Builder diperlukan NutritionalFacts n = new NutritionalFacts.Builder (10) .carbo (23) .fat (1) .build ();

Saya tidak mengerti apa arti pesan itu. Tolong jelaskan. Kode di atas mirip dengan contoh yang disarankan oleh Bloch dalam bukunya.

Swaranga Sarma
sumber
1
kemungkinan duplikat dari Sebuah contoh terlampir yang berisi <referensi saya> diperlukan
Joshua Taylor

Jawaban:

172

Jadikan pembangun itu statickelas. Maka itu akan berhasil. Jika non-statis, itu akan membutuhkan sebuah instance dari kelasnya sendiri - dan intinya bukanlah untuk memiliki sebuah instance, dan bahkan untuk melarang membuat instance tanpa pembangun.

public class NutritionFacts {
    public static class Builder {
    }
}

Referensi: Kelas bertingkat

Bozho
sumber
34
Dan, kenyataannya, Builderada staticdi contoh di buku (halaman 14, baris 10 di Edisi ke-2).
Powerlord
27

Anda harus menjadikan kelas Builder sebagai statis dan Anda juga harus membuat kolom menjadi final dan memiliki getter untuk mendapatkan nilai tersebut. Jangan berikan penyetel untuk nilai-nilai itu. Dengan cara ini kelas Anda tidak akan pernah berubah.

public class NutritionalFacts {
    private final int sodium;
    private final int fat;
    private final int carbo;

    public int getSodium(){
        return sodium;
    }

    public int getFat(){
        return fat;
    }

    public int getCarbo(){
        return carbo;
    }

    public static class Builder {
        private int sodium;
        private int fat;
        private int carbo;

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

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

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

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

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

Dan sekarang Anda dapat mengatur properti sebagai berikut:

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();
Raj Hassani
sumber
Mengapa tidak membuat bidang NutritionalFacts menjadi publik? Mereka sudah final, dan itu akan tetap tidak berubah.
skia.heliou
finalbidang masuk akal hanya jika bidang selalu dibutuhkan selama inisialisasi. Jika tidak, maka bidang tidak seharusnya final.
Piotrek Hryciuk
13

Untuk menghasilkan pembangun dalam di Intellij IDEA, lihat plugin ini: https://github.com/analytically/innerbuilder

secara analitis
sumber
2
Ini tidak ada hubungannya dengan pertanyaan yang diajukan tetapi sangat membantu! Temuan bagus!
The Hungry Androider
12

Anda mencoba mengakses kelas non-statis dengan cara statis. Ubah Builderke static class Builderdan itu harus berhasil.

Penggunaan contoh yang Anda berikan gagal karena tidak ada contoh Buildersaat ini. Kelas statis untuk semua tujuan praktis selalu dibuat. Jika Anda tidak membuatnya statis, Anda perlu mengatakan:

Widget = new Widget.Builder(10).setparm1(1).setparm2(3).build();

Karena Anda perlu membangun yang baru Buildersetiap saat.

Michael K
sumber
8

Anda perlu mendeklarasikan Builderkelas dalam sebagai static.

Lihat beberapa dokumentasi untuk kelas dalam non-statis dan kelas dalam statis .

Pada dasarnya instance kelas dalam non-statis tidak dapat ada tanpa instance kelas luar yang dilampirkan.

Grzegorz Oledzki
sumber
5

Setelah Anda mendapatkan ide, dalam praktiknya, Anda mungkin akan merasa lombok @Builderjauh lebih nyaman.

@Builder memungkinkan Anda secara otomatis menghasilkan kode yang diperlukan agar kelas Anda dapat digunakan dengan kode seperti:

Person.builder()
  .name("Adam Savage")
  .city("San Francisco")
  .job("Mythbusters")
  .job("Unchained Reaction")
 .build(); 

Dokumentasi resmi: https://www.projectlombok.org/features/Builder

torina
sumber
4

Ini berarti Anda tidak dapat membuat tipe lampiran. Ini berarti bahwa pertama-tama Anda harus membuat instance kelas "induk", lalu dari instance ini Anda dapat membuat instance kelas bersarang.

NutritionalFacts n = new NutritionalFacts()

Builder b = new n.Builder(10).carbo(23).fat(1).build();

Kelas Bersarang

Damian Leszczyński - Vash
sumber
3
itu tidak masuk akal, karena dia membutuhkan pembangun untuk membangun "fakta", bukan sebaliknya.
Bozho
5
benar jika kita fokus pada pola pembangun, saya hanya fokus pada "Saya tidak mengerti apa arti pesan itu", dan menyajikan salah satu dari dua solusi.
Damian Leszczyński - Vash
3

Kelas Builder harus statis. Saya tidak punya waktu sekarang untuk benar-benar menguji kode di luar itu, tetapi jika tidak berhasil, beri tahu saya dan saya akan melihatnya lagi.

Shaun
sumber
1

Saya pribadi lebih suka menggunakan pendekatan lain, bila Anda memiliki 2 kelas yang berbeda. Jadi, Anda tidak memerlukan kelas statis apa pun. Ini pada dasarnya untuk menghindari penulisan Class.Buildersaat Anda harus membuat instance baru.

public class Person {
    private String attr1;
    private String attr2;
    private String attr3;

    // package access
    Person(PersonBuilder builder) {
        this.attr1 = builder.getAttr1();
        // ...
    }

    // ...
    // getters and setters 
}

public class PersonBuilder (
    private String attr1;
    private String attr2;
    private String attr3;

    // constructor with required attribute
    public PersonBuilder(String attr1) {
        this.attr1 = attr1;
    }

    public PersonBuilder setAttr2(String attr2) {
        this.attr2 = attr2;
        return this;
    }

    public PersonBuilder setAttr3(String attr3) {
        this.attr3 = attr3;
        return this;
    }

    public Person build() {
        return new Person(this);
    }
    // ....
}

Jadi, Anda dapat menggunakan pembangun Anda seperti ini:

Person person = new PersonBuilder("attr1")
                            .setAttr2("attr2")
                            .build();
sidik jari
sumber
0

Seperti yang telah disebutkan di sini, Anda perlu membuat kelas static. Hanya tambahan kecil - jika Anda mau, ada cara yang sedikit berbeda tanpa statis.

Pertimbangkan ini. Mengimplementasikan pembangun dengan mendeklarasikan sesuatu seperti withProperty(value)penyetel tipe di dalam kelas dan membuatnya mengembalikan referensi ke dirinya sendiri. Dalam pendekatan ini, Anda memiliki kelas tunggal dan elegan yang merupakan thread yang aman dan ringkas.

Pertimbangkan ini:

public class DataObject {

    private String first;
    private String second;
    private String third;

    public String getFirst(){
       return first; 
    }

    public void setFirst(String first){
       this.first = first; 
    }

    ... 

    public DataObject withFirst(String first){
       this.first = first;
       return this; 
    }

    public DataObject withSecond(String second){
       this.second = second;
       return this; 
    }

    public DataObject withThird(String third){
       this.third = third;
       return this; 
    }
}


DataObject dataObject = new DataObject()
     .withFirst("first data")
     .withSecond("second data")
     .withThird("third data");

Lihat untuk contoh Java Builder lainnya.

Johnny
sumber
0

Anda perlu mengubah kelas Builder menjadi kelas statis Builder . Maka itu akan bekerja dengan baik.

krishna kirti
sumber