Penjanaan Automatik Corak Pembangun dengan FreeBuilder

1. Gambaran keseluruhan

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

2. Corak Reka Bina

Builder adalah salah satu Corak Reka Bentuk Penciptaan yang paling banyak digunakan dalam bahasa berorientasikan objek. Ia mengaburkan contoh objek domain yang kompleks dan menyediakan API yang fasih untuk membuat instance. Oleh itu, ia membantu mengekalkan lapisan domain yang ringkas.

Walaupun kegunaannya, pembangun umumnya kompleks untuk dilaksanakan, terutama di Jawa. Objek nilai yang lebih sederhana malah memerlukan banyak kod plat boiler.

3. Pelaksanaan Pembangun di Jawa

Sebelum kita meneruskan dengan FreeBuilder, mari kita laksanakan pembina plat boiler untuk kelas Karyawan kami :

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 Pembina dalaman :

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

Oleh itu, kita sekarang boleh menggunakan pembangun untuk memberi contoh objek Karyawan :

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

Seperti yang ditunjukkan di atas, banyak kod pelat boiler diperlukan untuk melaksanakan kelas pembina.

Di bahagian kemudian, kita akan melihat bagaimana FreeBuilder dapat dengan mudah mempermudah pelaksanaan ini.

4. Ketergantungan Maven

Untuk menambahkan perpustakaan FreeBuilder, kami akan menambahkan pergantungan FreeBuilder Maven di pom.xml kami :

 org.inferred freebuilder 2.4.1 

5. Anotasi FreeBuilder

5.1. Menjana Pembina

FreeBuilder adalah perpustakaan sumber terbuka yang membantu pembangun menghindari kod boilerplate semasa melaksanakan kelas pembangun. Ini menggunakan pemrosesan anotasi di Jawa untuk menghasilkan pelaksanaan konkrit dari pola pembangun.

Kami akan memberi penjelasan kepada kelas Karyawan kami dari bahagian sebelumnya dengan @ FreeBuilder dan melihat bagaimana kelas ini menghasilkan kelas pembangun secara automatik:

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

Penting untuk menunjukkan bahawa Karyawan kini menjadi antara muka dan bukannya kelas POJO. Selanjutnya, ia mengandungi semua atribut objek Karyawan sebagai kaedah.

Sebelum kita terus menggunakan pembangun ini, kita mesti mengkonfigurasi IDE kita untuk mengelakkan masalah penyusunan. Oleh kerana FreeBuilder menghasilkan kelas Employee_Builder secara automatik semasa penyusunan, IDE biasanya mengadu ClassNotFoundException pada nombor nombor 8 .

Untuk mengelakkan masalah seperti itu, kita perlu mengaktifkan pemprosesan anotasi di IntelliJ atau Eclipse . Dan semasa melakukannya, kami akan menggunakan org.inferred.freebuilder.processor.Processor pemproses anotasi FreeBuilder. Selain itu, direktori yang digunakan untuk menghasilkan fail sumber ini harus ditandakan sebagai Generated Sumber Root.

Sebagai alternatif, kita juga boleh melaksanakan pemasangan mvn untuk membina projek dan menghasilkan kelas pembina yang diperlukan.

Akhirnya, kami telah menyusun projek kami dan kini dapat menggunakan kelas Employee.Builder :

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

Secara keseluruhan, terdapat dua perbezaan utama antara ini dan kelas pembina yang kita lihat sebelumnya. Pertama, kita mesti menetapkan nilai untuk semua atribut kelas Pekerja . Jika tidak, ia membuang IllegalStateException .

Kami akan melihat bagaimana FreeBuilder menangani atribut pilihan di bahagian kemudian.

Kedua, nama kaedah Employee.Builder tidak mengikuti konvensyen penamaan JavaBean. Kami akan melihatnya di bahagian seterusnya.

5.2. Konvensyen Penamaan JavaBean

Untuk menegakkan FreeBuilder untuk mengikuti konvensi penamaan JavaBean, kita harus menamakan semula kaedah kita di Employee dan awalan kaedah dengan get :

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

Ini akan menghasilkan pemula dan pengatur yang mengikuti konvensyen penamaan JavaBean:

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

5.3. Kaedah Mapper

Ditambah dengan getter dan setter, FreeBuilder juga menambah kaedah mapper di kelas builder. Kaedah mapper ini menerima UnaryOperator sebagai input, sehingga memungkinkan pembangun untuk menghitung nilai bidang yang kompleks.

Anggaplah kelas Pekerja kita juga mempunyai bidang gaji:

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

Sekarang anggap kita perlu menukar mata wang 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 kaedah mapper seperti itu untuk semua bidang.

6. Nilai Lalai dan Pemeriksaan Kekangan

6.1. Menetapkan Nilai Lalai

The Employee.Builder pelaksanaan yang telah kita bincangkan setakat ini menjangka pelanggan untuk lulus nilai untuk semua bidang. Sebenarnya, ia gagal dalam proses inisialisasi dengan IllegalStateException sekiranya terdapat ladang 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

Pelaksanaan pembangun yang telah kita bincangkan dalam artikel ini sangat serupa dengan pelaksanaan Lombok, Immutables, atau pemproses anotasi lain. Walau bagaimanapun, terdapat beberapa ciri membezakan yang telah kita bincangkan:

    • Kaedah mapper
    • Jenis Boleh Dibangun Bersarang
    • Objek Separa

13. Kesimpulannya

Dalam artikel ini, kami menggunakan perpustakaan FreeBuilder untuk menghasilkan kelas pembangun di Java. Kami melaksanakan pelbagai penyesuaian kelas pembangun dengan bantuan anotasi, sehingga mengurangkan kod pelat boiler yang diperlukan untuk pelaksanaannya .

Kami juga melihat bagaimana FreeBuilder berbeza dari beberapa perpustakaan lain dan secara ringkas membincangkan beberapa ciri tersebut dalam artikel ini.

Semua contoh kod boleh didapati di GitHub.