デフォルト値のロンボクビルダー
1. 前書き
このクイックチュートリアルでは、Lombokでビルダーパターンを使用するときに、属性のデフォルト値を提供する方法を調査します。
intro to Lombokも確認してください。
2. 依存関係
このチュートリアルではLombokを使用します。そのために必要な依存関係は、次の1つだけです。
org.projectlombok
lombok
1.16.18
provided
3. Lombok Builderを使用したPOJO
まず、Lombokがビルダーパターンの実装に必要な定型コードを取り除くのにどのように役立つかを見てみましょう。
簡単なPOJOから始めましょう。
public class Pojo {
private String name;
private boolean original;
}
このクラスを役立てるには、ゲッターが必要です。 また、たとえば、このクラスをORMで使用する場合は、デフォルトのコンストラクターが必要になる可能性があります。
これらの上に、このクラスのビルダーが必要です。 Lombokを使用すると、いくつかの簡単な注釈を使用してこれらすべてを実現できます。
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Pojo {
private String name;
private boolean original;
}
4. 期待の定義
ユニットテストの形で達成したいことに対するいくつかの期待を定義しましょう。
最初の基本的な要件は、ビルダーでオブジェクトをビルドした後のデフォルト値の存在です。
@Test
public void givenBuilderWithDefaultValue_ThanDefaultValueIsPresent() {
Pojo build = Pojo.builder()
.build();
Assert.assertEquals("foo", build.getName());
Assert.assertTrue(build.isOriginal());
}
Of course, this test fails since the @Builder annotation doesn’t populate values.これはまもなく修正されます。
ORMを使用する場合、通常はデフォルトのコンストラクターに依存します。 そのため、デフォルトのコンストラクターから、ビルダーからの動作と同じ動作を期待する必要があります。
@Test
public void givenBuilderWithDefaultValue_NoArgsWorksAlso() {
Pojo build = Pojo.builder()
.build();
Pojo pojo = new Pojo();
Assert.assertEquals(build.getName(), pojo.getName());
Assert.assertTrue(build.isOriginal() == pojo.isOriginal());
}
この段階で、このテストは合格です。
では、どうすれば両方のテストに合格できるか見てみましょう。
5. LombokのBuilder.Defaultアノテーション
Lombok v1.16.16以降、@Builderの内部アノテーションを使用できます。
// class annotations as before
public class Pojo {
@Builder.Default
private String name = "foo";
@Builder.Default
private boolean original = true;
}
シンプルで読みやすいですが、いくつかの欠点があります。
これにより、ビルダーにはデフォルト値が存在し、最初のテストケースがパスします。 Unfortunately, the no-args constructor won’t get the default values, making the second test case fail。 引数なしのコンストラクターが生成されていないが、明示的に記述されている場合でも。
Builder.Defaultアノテーションのこの副作用は最初から存在しており、おそらく長い間存在するでしょう。
6. Builderを初期化します
最小限のビルダー実装でデフォルト値を定義することで、両方のテストに合格させることができます。
// class annotations as before
public class Pojo {
private String name = "foo";
private boolean original = true;
public static class PojoBuilder {
private String name = "foo";
private boolean original = true;
}
}
このようにして、両方のテストに合格します。
残念ながら、価格はコードの重複です。 For a POJO with tens of fields, it could be error prone to maintain the double initialization.
ただし、この価格を支払う意思がある場合は、もう1つ注意する必要があります。 IDE内のリファクタリングを使用してクラスの名前を変更した場合、静的内部クラスの名前は自動的に変更されません。 その後、Lombokはそれを見つけられず、コードが壊れます。
このリスクを排除するために、ビルダーアノテーションを修飾できます。
// class annotations as before
@Builder(builderClassName = "PojoBuilder")
public class Pojo {
private String name = "foo";
private boolean original = true;
public static class PojoBuilder {
private String name = "foo";
private boolean original = true;
}
}
7. toBuilderの使用
@Builder は、元のクラスのインスタンスからビルダーのインスタンスを生成することもサポートしています。 この機能はデフォルトでは有効になっていません。 ビルダーアノテーションでtoBuilderパラメータを設定することで有効にできます。
// class annotations as before
@Builder(toBuilder = true)
public class Pojo {
private String name = "foo";
private boolean original = true;
}
これで、we can get rid of the double initialization。
もちろん、そのための価格があります。 We have to instantiate the class to create a builder.したがって、テストも変更する必要があります。
@Test
public void givenBuilderWithDefaultValue_ThenDefaultValueIsPresent() {
Pojo build = new Pojo().toBuilder()
.build();
Assert.assertEquals("foo", build.getName());
Assert.assertTrue(build.isOriginal());
}
@Test
public void givenBuilderWithDefaultValue_thenNoArgsWorksAlso() {
Pojo build = new Pojo().toBuilder()
.build();
Pojo pojo = new Pojo();
Assert.assertEquals(build.getName(), pojo.getName());
Assert.assertTrue(build.isOriginal() == pojo.isOriginal());
}
繰り返しになりますが、両方のテストに合格するため、no-argsコンストラクターを使用した場合のデフォルト値はビルダーを使用した場合と同じです。
8. 結論
そこで、Lombokビルダーのデフォルト値を提供するためのいくつかのオプションを検討しました。
Builder。Defaultアノテーションの副作用は、注意を払う価値があります。 しかし、他のオプションにも欠点があります。 そのため、現在の状況に基づいて慎重に選択する必要があります。
いつものように、コードはover on GitHubで利用できます。