FreeBuilderを使用したビルダーパターンの自動生成
1. 概要
このチュートリアルでは、 FreeBuilderライブラリを使用して、Javaでビルダークラスを生成します。
2. Builderのデザインパターン
Builderは、オブジェクト指向言語で最も広く使用されている Creation DesignPatternsの1つです。 は、複雑なドメインオブジェクトのインスタンス化を抽象化し、インスタンスを作成するための流暢なAPIを提供します。 これにより、簡潔なドメイン層を維持するのに役立ちます。
その有用性にもかかわらず、ビルダーは、特にJavaで、一般的に実装が複雑です。 さらに単純な値オブジェクトには、多くの定型コードが必要です。
3. Javaでのビルダーの実装
FreeBuilderに進む前に、Employeeクラスの定型文を実装しましょう。
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;
}
}
そして、内部の Builder クラス:
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);
}
}
したがって、ビルダーを使用してEmployeeオブジェクトをインスタンス化できるようになりました。
Employee.Builder emplBuilder = new Employee.Builder();
Employee employee = emplBuilder
.setName("baeldung")
.setAge(12)
.setDepartment("Builder Pattern")
.build();
上に示したように、ビルダークラスを実装するには多くの定型コードが必要です。
後のセクションでは、FreeBuilderがこの実装を即座に簡素化する方法を説明します。
4. Mavenの依存関係
FreeBuilderライブラリを追加するには、FreeBuilderMaven依存関係をpom.xmlに追加します。
<dependency>
<groupId>org.inferred</groupId>
<artifactId>freebuilder</artifactId>
<version>2.4.1</version>
</dependency>
5. FreeBuilderアノテーション
5.1. ビルダーの生成
FreeBuilderは、開発者がビルダークラスを実装する際にボイラープレートコードを回避するのに役立つオープンソースライブラリです。 Javaでの注釈処理を利用して、ビルダーパターンの具体的な実装を生成します。
前のセクションのEmployeeクラスに@ FreeBuilder でアノテーションを付け、ビルダークラスが自動的に生成される方法を確認します。
@FreeBuilder
public interface Employee {
String name();
int age();
String department();
class Builder extends Employee_Builder {
}
}
EmployeeがPOJOクラスではなくインターフェースになったことを指摘することが重要です。 さらに、Employeeオブジェクトのすべての属性がメソッドとして含まれています。
このビルダーを引き続き使用する前に、コンパイルの問題を回避するようにIDEを構成する必要があります。 FreeBuilderはコンパイル中にEmployee_Builderクラスを自動的に生成するため、IDEは通常行番号8のClassNotFoundExceptionについて文句を言います。
このような問題を回避するには、IntelliJまたはEclipseで注釈処理を有効にする必要があります。 その際、FreeBuilderのアノテーションプロセッサを使用します
または、 mvn install を実行してプロジェクトをビルドし、必要なビルダークラスを生成することもできます。
最後に、プロジェクトをコンパイルし、Employee.Builderクラスを使用できるようになりました。
Employee.Builder builder = new Employee.Builder();
Employee employee = builder.name("baeldung")
.age(10)
.department("Builder Pattern")
.build();
全体として、これと以前に見たビルダークラスの間には2つの主な違いがあります。 初め、
FreeBuilderがオプションの属性をどのように処理するかについては、後のセクションで説明します。
次に、 Employee.Builder のメソッド名は、JavaBeanの命名規則に従っていません。 これについては、次のセクションで説明します。
5.2. JavaBean命名規則
FreeBuilderがJavaBeanの命名規則に従うように強制するには、Employeeのメソッドの名前を変更し、メソッドの前に get:を付ける必要があります。
@FreeBuilder
public interface Employee {
String getName();
int getAge();
String getDepartment();
class Builder extends Employee_Builder {
}
}
これにより、JavaBeanの命名規則に従うゲッターとセッターが生成されます。
Employee employee = builder
.setName("baeldung")
.setAge(10)
.setDepartment("Builder Pattern")
.build();
5.3. マッパーメソッド
ゲッターとセッターと組み合わせて、FreeBuilderはビルダークラスにマッパーメソッドも追加します。 これらのマッパーメソッドは、入力としてUnaryOperator を受け入れるため、開発者は複雑なフィールド値を計算できます。
従業員クラスにも給与フィールドがあるとします。
@FreeBuilder
public interface Employee {
Optional<Double> getSalaryInUSD();
}
ここで、入力として提供される給与の通貨を変換する必要があるとします。
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は、すべてのフィールドにそのようなマッパーメソッドを提供します。
6. デフォルト値と制約チェック
6.1. デフォルト値の設定
これまでに説明したEmployee.Builderの実装では、クライアントがすべてのフィールドの値を渡すことを想定しています。 実際のところ、フィールドが欠落している場合は、IllegalStateExceptionで初期化プロセスに失敗します。
このような障害を回避するために、フィールドのデフォルト値を設定するか、オプションにすることができます。
Employee.Builderコンストラクターでデフォルト値を設定できます。
@FreeBuilder
public interface Employee {
// getter methods
class Builder extends Employee_Builder {
public Builder() {
setDepartment("Builder Pattern");
}
}
}
したがって、コンストラクターでデフォルトの部門を設定するだけです。 この値は、すべての従業員オブジェクトに適用されます。
6.2. 制約チェック
通常、フィールド値には特定の制約があります。 たとえば、有効なメールには「@」が含まれている必要があります。または、従業員の年齢が範囲内である必要があります。
このような制約により、入力値を検証する必要があります。 また、 FreeBuilderを使用すると、セッターメソッドをオーバーライドするだけでこれらの検証を追加できます。
@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. オプション値
7.1. オプションフィールドの使用
一部のオブジェクトにはオプションのフィールドが含まれており、その値は空またはnullにすることができます。 FreeBuilderでは、Javaオプション型を使用してこのようなフィールドを定義できます。
@FreeBuilder
public interface Employee {
String getName();
int getAge();
// other getters
Optional<Boolean> getPermanent();
Optional<String> getDateOfJoining();
class Builder extends Employee_Builder {
}
}
ここで、オプションのフィールドに値を指定することをスキップできます。
Employee employee = builder.setName("baeldung")
.setAge(10)
.setPermanent(true)
.build();
特に、次の値を渡しただけです。 永続の代わりにフィールド
7.2. @Nullableフィールドの使用
Javaでnullを処理するには、オプションのを使用することをお勧めしますが、FreeBuilderではusが@Nullableを使用して下位互換性を保つことができます。
@FreeBuilder
public interface Employee {
String getName();
int getAge();
// other getter methods
Optional<Boolean> getPermanent();
Optional<String> getDateOfJoining();
@Nullable String getCurrentProject();
class Builder extends Employee_Builder {
}
}
オプションの使用は、場合によっては不適切です。これが、ビルダークラスに@Nullableが推奨されるもう1つの理由です。
8. コレクションと地図
FreeBuilderは、コレクションとマップを特別にサポートしています。
@FreeBuilder
public interface Employee {
String getName();
int getAge();
// other getter methods
List<Long> getAccessTokens();
Map<String, Long> getAssetsSerialIdMapping();
class Builder extends Employee_Builder {
}
}
FreeBuilderは、コンビニエンスメソッドを追加して、ビルダークラスのコレクションに入力要素を追加します。
Employee employee = builder.setName("baeldung")
.setAge(10)
.addAccessTokens(1221819L)
.addAccessTokens(1223441L, 134567L)
.build();
ビルダークラスにはgetAccessTokens()メソッドもあり、は変更不可能なリストを返します。 同様に、マップの場合:
Employee employee = builder.setName("baeldung")
.setAge(10)
.addAccessTokens(1221819L)
.addAccessTokens(1223441L, 134567L)
.putAssetsSerialIdMapping("Laptop", 12345L)
.build();
Map alsoのgetterメソッドは、変更できないマップをクライアントコードに返します。
9. ネストされたビルダー
実際のアプリケーションでは、ドメインエンティティの多くの値オブジェクトをネストする必要がある場合があります。 また、ネストされたオブジェクト自体にビルダーの実装が必要になる可能性があるため、FreeBuilderではネストされたビルド可能なタイプを使用できます。
たとえば、Employeeクラスにネストされた複合型Addressがあるとします。
@FreeBuilder
public interface Address {
String getCity();
class Builder extends Address_Builder {
}
}
これで、FreeBuilderは、 AddresstypeとともにAddress.Builderを入力として受け取るsetterメソッドを生成します。
Address.Builder addressBuilder = new Address.Builder();
addressBuilder.setCity(CITY_NAME);
Employee employee = builder.setName("baeldung")
.setAddress(addressBuilder)
.build();
特に、FreeBuilderは、 Employee:の既存のAddressオブジェクトをカスタマイズするためのメソッドも追加します。
Employee employee = builder.setName("baeldung")
.setAddress(addressBuilder)
.mutateAddress(a -> a.setPinCode(112200))
.build();
FreeBuilder タイプに加えて、FreeBuilderではprotosなどの他のビルダーをネストすることもできます。
10. 部分オブジェクトの構築
前に説明したように、FreeBuilderは、制約違反(たとえば、必須フィールドの値の欠落)に対してIllegalStateExceptionをスローします。
これは実稼働環境に望ましいものですが、一般的な制約に依存しない単体テストを複雑にします。
このような制約を緩和するために、FreeBuilderでは部分的なオブジェクトを作成できます。
Employee employee = builder.setName("baeldung")
.setAge(10)
.setEmail("[email protected]")
.buildPartial();
assertNotNull(employee.getEmail());
したがって、 Employee のすべての必須フィールドを設定していなくても、emailフィールドに有効な値があることを確認できます。
11. カスタムtoString()メソッド
値オブジェクトを使用すると、
@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{
}
}
Employee をインターフェイスではなく抽象クラスとして宣言し、カスタムの toString()実装を提供しました。
12. 他のビルダーライブラリとの比較
この記事で説明したビルダーの実装は、 Lombok 、 Immutables 、またはその他のアノテーションプロセッサーの実装と非常によく似ています。 ただし、いくつかの特徴的な特性については、すでに説明しました。
-
- マッパーメソッド
- ネストされたビルド可能なタイプ
- 部分オブジェクト
13. 結論
この記事では、FreeBuilderライブラリを使用してJavaでビルダークラスを生成しました。 アノテーションを使用してビルダークラスのさまざまなカスタマイズを実装しました。これにより、実装に必要なボイラープレートコードが削減されました。
また、FreeBuilderが他のライブラリとどのように異なるかを確認し、この記事でそれらの特性のいくつかについて簡単に説明しました。
すべてのコード例は、GitHubで入手できます。