Pembuatan Otomatis Pola Builder dengan FreeBuilder

1. Ikhtisar

Dalam tutorial ini, kita akan menggunakan pustaka FreeBuilder untuk menghasilkan kelas pembangun di Java.

2. Pola Desain Pembangun

Builder adalah salah satu Pola Desain Kreasi yang paling banyak digunakan dalam bahasa berorientasi objek. Ini mengabstraksi instance objek domain yang kompleks dan menyediakan API yang lancar untuk membuat instance. Dengan demikian membantu mempertahankan lapisan domain yang ringkas.

Terlepas dari kegunaannya, builder umumnya kompleks untuk diterapkan, terutama di Java. Objek nilai yang lebih sederhana pun memerlukan banyak kode boilerplate.

3. Implementasi Builder di Java

Sebelum kita melanjutkan dengan FreeBuilder, mari terapkan pembuat boilerplate untuk kelas Karyawan kita :

public class Employee { private final String name; private final int age; private final String department; private Employee(String name, int age, String department) { this.name = name; this.age = age; this.department = department; } }

Dan kelas Builder batin :

public static class Builder { private String name; private int age; private String department; public Builder setName(String name) { this.name = name; return this; } public Builder setAge(int age) { this.age = age; return this; } public Builder setDepartment(String department) { this.department = department; return this; } public Employee build() { return new Employee(name, age, department); } }

Karenanya, sekarang kita dapat menggunakan builder untuk membuat instance objek Employee :

Employee.Builder emplBuilder = new Employee.Builder(); Employee employee = emplBuilder .setName("baeldung") .setAge(12) .setDepartment("Builder Pattern") .build();

Seperti yang ditunjukkan di atas, banyak kode boilerplate diperlukan untuk mengimplementasikan kelas builder.

Di bagian selanjutnya, kita akan melihat bagaimana FreeBuilder dapat langsung menyederhanakan implementasi ini.

4. Ketergantungan Maven

Untuk menambahkan pustaka FreeBuilder, kami akan menambahkan ketergantungan FreeBuilder Maven di pom.xml kami :

 org.inferred freebuilder 2.4.1 

5. Anotasi FreeBuilder

5.1. Menghasilkan Builder

FreeBuilder adalah pustaka sumber terbuka yang membantu pengembang menghindari kode boilerplate saat mengimplementasikan kelas pembangun. Itu menggunakan pemrosesan anotasi di Jawa untuk menghasilkan implementasi konkret dari pola pembangun.

Kami akan memberi anotasi kelas Karyawan kami dari bagian sebelumnya dengan @ FreeBuilder dan melihat bagaimana kelas pembangun secara otomatis menghasilkan:

@FreeBuilder public interface Employee { String name(); int age(); String department(); class Builder extends Employee_Builder { } }

Penting untuk diketahui bahwa Karyawan sekarang menjadi antarmuka daripada kelas POJO. Selain itu, ini berisi semua atribut objek Karyawan sebagai metode.

Sebelum kita melanjutkan menggunakan pembuat ini, kita harus mengkonfigurasi IDE kita untuk menghindari masalah kompilasi. Karena FreeBuilder secara otomatis menghasilkan kelas Employee_Builder selama kompilasi, IDE biasanya mengeluhkan ClassNotFoundException pada baris nomor 8 .

Untuk menghindari masalah seperti itu, kita perlu mengaktifkan pemrosesan anotasi di IntelliJ atau Eclipse . Dan saat melakukannya, kita akan menggunakan pemroses anotasi FreeBuilder org.inferred.freebuilder.processor.Processor. Selain itu, direktori yang digunakan untuk membuat file sumber ini harus ditandai sebagai Root Sumber yang Dihasilkan.

Alternatifnya, kita juga dapat menjalankan mvn install untuk membangun proyek dan menghasilkan kelas pembangun yang diperlukan.

Akhirnya, kami telah menyusun proyek kami dan sekarang dapat menggunakan kelas Employee.Builder :

Employee.Builder builder = new Employee.Builder(); Employee employee = builder.name("baeldung") .age(10) .department("Builder Pattern") .build();

Secara keseluruhan, ada dua perbedaan utama antara ini dan kelas pembangun yang kita lihat sebelumnya. Pertama, kita harus menetapkan nilai untuk semua atribut kelas Karyawan . Jika tidak, ini akan melontarkan IllegalStateException .

Kita akan melihat bagaimana FreeBuilder menangani atribut opsional di bagian selanjutnya.

Kedua, nama metode Employee.Builder tidak mengikuti konvensi penamaan JavaBean. Kami akan melihat ini di bagian selanjutnya.

5.2. Konvensi Penamaan JavaBean

Untuk memaksa FreeBuilder mengikuti konvensi penamaan JavaBean, kita harus mengganti nama metode di Employee dan mengawali metode dengan get :

@FreeBuilder public interface Employee { String getName(); int getAge(); String getDepartment(); class Builder extends Employee_Builder { } }

Ini akan menghasilkan getter dan setter yang mengikuti konvensi penamaan JavaBean:

Employee employee = builder .setName("baeldung") .setAge(10) .setDepartment("Builder Pattern") .build();

5.3. Metode Mapper

Digabung dengan getter dan setter, FreeBuilder juga menambahkan metode mapper di kelas builder. Metode mapper ini menerima UnaryOperator sebagai masukan, sehingga memungkinkan pengembang untuk menghitung nilai bidang yang kompleks.

Misalkan kelas Karyawan kita juga memiliki bidang gaji:

@FreeBuilder public interface Employee { Optional getSalaryInUSD(); }

Sekarang misalkan kita perlu mengonversi mata uang dari gaji yang diberikan sebagai input:

long salaryInEuros = INPUT_SALARY_EUROS; Employee.Builder builder = new Employee.Builder(); Employee employee = builder .setName("baeldung") .setAge(10) .mapSalaryInUSD(sal -> salaryInEuros * EUROS_TO_USD_RATIO) .build();

FreeBuilder menyediakan metode mapper seperti itu untuk semua bidang.

6. Nilai Default dan Pemeriksaan Kendala

6.1. Mengatur Nilai Default

The Employee.Builder implementasi yang telah kita bahas sejauh mengharapkan klien untuk lulus nilai untuk semua bidang. Faktanya, ini gagal dalam proses inisialisasi dengan IllegalStateException jika ada bidang yang hilang.

In order to avoid such failures, we can either set default values for fields or make them optional.

We can set default values in the Employee.Builder constructor:

@FreeBuilder public interface Employee { // getter methods class Builder extends Employee_Builder { public Builder() { setDepartment("Builder Pattern"); } } }

So we simply set the default department in the constructor. This value will apply to all Employee objects.

6.2. Constraint Checks

Usually, we have certain constraints on field values. For example, a valid email must contain an “@” or the age of an Employee must be within a range.

Such constraints require us to put validations on input values. And FreeBuilder allows us to add these validations by merely overriding the setter methods:

@FreeBuilder public interface Employee { // getter methods class Builder extends Employee_Builder { @Override public Builder setEmail(String email) { if (checkValidEmail(email)) return super.setEmail(email); else throw new IllegalArgumentException("Invalid email"); } private boolean checkValidEmail(String email) { return email.contains("@"); } } }

7. Optional Values

7.1. Using Optional Fields

Some objects contain optional fields, the values for which can be empty or null. FreeBuilder allows us to define such fields using the Java Optional type:

@FreeBuilder public interface Employee { String getName(); int getAge(); // other getters Optional getPermanent(); Optional getDateOfJoining(); class Builder extends Employee_Builder { } }

Now we may skip providing any value for Optional fields:

Employee employee = builder.setName("baeldung") .setAge(10) .setPermanent(true) .build();

Notably, we simply passed the value for permanent field instead of an Optional. Since we didn't set the value for dateOfJoining field, it will be Optional.empty() which is the default for Optional fields.

7.2. Using @Nullable Fields

Although using Optional is recommended for handling nulls in Java, FreeBuilder allows us to use @Nullable for backward compatibility:

@FreeBuilder public interface Employee { String getName(); int getAge(); // other getter methods Optional getPermanent(); Optional getDateOfJoining(); @Nullable String getCurrentProject(); class Builder extends Employee_Builder { } }

The use of Optional is ill-advised in some cases which is another reason why @Nullable is preferred for builder classes.

8. Collections and Maps

FreeBuilder has special support for collections and maps:

@FreeBuilder public interface Employee { String getName(); int getAge(); // other getter methods List getAccessTokens(); Map getAssetsSerialIdMapping(); class Builder extends Employee_Builder { } }

FreeBuilder adds convenience methods to add input elements into the Collection in the builder class:

Employee employee = builder.setName("baeldung") .setAge(10) .addAccessTokens(1221819L) .addAccessTokens(1223441L, 134567L) .build();

There is also a getAccessTokens() method in the builder class which returns an unmodifiable list. Similarly, for Map:

Employee employee = builder.setName("baeldung") .setAge(10) .addAccessTokens(1221819L) .addAccessTokens(1223441L, 134567L) .putAssetsSerialIdMapping("Laptop", 12345L) .build();

The getter method for Map also returns an unmodifiable map to the client code.

9. Nested Builders

For real-world applications, we may have to nest a lot of value objects for our domain entities. And since the nested objects can themselves need builder implementations, FreeBuilder allows nested buildable types.

For example, suppose we have a nested complex type Address in the Employee class:

@FreeBuilder public interface Address { String getCity(); class Builder extends Address_Builder { } }

Now, FreeBuilder generates setter methods that take Address.Builder as an input together with Address type:

Address.Builder addressBuilder = new Address.Builder(); addressBuilder.setCity(CITY_NAME); Employee employee = builder.setName("baeldung") .setAddress(addressBuilder) .build();

Notably, FreeBuilder also adds a method to customize the existing Address object in the Employee:

Employee employee = builder.setName("baeldung") .setAddress(addressBuilder) .mutateAddress(a -> a.setPinCode(112200)) .build();

Along with FreeBuilder types, FreeBuilder also allows nesting of other builders such as protos.

10. Building Partial Object

As we've discussed before, FreeBuilder throws an IllegalStateException for any constraint violation — for instance, missing values for mandatory fields.

Although this is desired for production environments, it complicates unit testing that is independent of constraints in general.

To relax such constraints, FreeBuilder allows us to build partial objects:

Employee employee = builder.setName("baeldung") .setAge(10) .setEmail("[email protected]") .buildPartial(); assertNotNull(employee.getEmail());

So, even though we haven't set all the mandatory fields for an Employee, we could still verify that the email field has a valid value.

11. Custom toString() Method

With value objects, we often need to add a custom toString() implementation. FreeBuilder allows this through abstract classes:

@FreeBuilder public abstract class Employee { abstract String getName(); abstract int getAge(); @Override public String toString() { return getName() + " (" + getAge() + " years old)"; } public static class Builder extends Employee_Builder{ } }

We declared Employee as an abstract class rather than an interface and provided a custom toString() implementation.

12. Comparison with Other Builder Libraries

Implementasi builder yang telah kita bahas dalam artikel ini sangat mirip dengan Lombok, Immutables, atau pemroses anotasi lainnya. Namun demikian, ada beberapa karakteristik pembeda yang telah kita bahas:

    • Metode mapper
    • Jenis yang Dapat Dibangun Bersarang
    • Objek Parsial

13. Kesimpulan

Pada artikel ini, kami menggunakan pustaka FreeBuilder untuk menghasilkan kelas pembangun di Java. Kami menerapkan berbagai penyesuaian kelas pembangun dengan bantuan anotasi, sehingga mengurangi kode boilerplate yang diperlukan untuk implementasinya .

Kami juga melihat bagaimana FreeBuilder berbeda dari beberapa perpustakaan lain dan secara singkat membahas beberapa karakteristik tersebut di artikel ini.

Semua contoh kode tersedia di GitHub.