JavaにおけるFluent Interfaceパターン:Fluent APIによるコード表現力の向上
別名
- Fluent API
- メソッドチェーン
Fluent Interfaceデザインパターンの目的
Fluent Interfaceパターンの主な目的は、メソッド呼び出しの連鎖(メソッドチェーン)によって、読みやすく流れるようなAPIを提供することです。これは、複雑なオブジェクトを段階的に構築し、開発者のエクスペリエンス全体を向上させるのに理想的なアプローチです。
実例を用いたFluent Interfaceパターンの詳細な説明
実例
コーヒーショップで注文を段階的にカスタマイズする場合を考えてみましょう。このアプローチは、JavaにおけるFluent Interfaceデザインパターンの動作と似ており、オブジェクトを順番に構築および構成するためにメソッド呼び出しを連鎖させることができます。バリスタに一度にすべてを伝えるのではなく、自然な流れで各カスタマイズステップを指定します。例えば、「ラージコーヒーにエスプレッソを2ショット追加して、砂糖なしでアーモンドミルクを入れてください」と言うかもしれません。このアプローチは、読みやすく直感的な方法でオブジェクトを構成するためにメソッド呼び出しを連鎖させるFluent Interfaceデザインパターンに似ています。コーヒー注文の各部分を順番に指定するように、Fluent Interfaceではメソッド呼び出しを連鎖させて、コード内でオブジェクトを段階的に構築および構成できます。
簡単に言うと
Fluent Interfaceパターンは、コードに対して読みやすく流れるようなインターフェースを提供します。
Wikipediaによると
ソフトウェアエンジニアリングにおいて、Fluent Interfaceとは、メソッドチェーンを多用したオブジェクト指向APIです。その目標は、ドメイン特化言語(DSL)を作成することでコードの可読性を向上させることです。
JavaにおけるFluent Interfaceパターンのプログラミング例
リストから異なる基準に基づいて数値を選択する必要があります。読みやすく使いやすい開発者エクスペリエンスを提供するために、Fluent Interfaceパターンを利用する絶好の機会です。
この例では、`FluentIterable`インターフェースの2つの実装が示されています。
public interface FluentIterable<E> extends Iterable<E> {
FluentIterable<E> filter(Predicate<? super E> predicate);
Optional<E> first();
FluentIterable<E> first(int count);
Optional<E> last();
FluentIterable<E> last(int count);
<T> FluentIterable<T> map(Function<? super E, T> function);
List<E> asList();
static <E> List<E> copyToList(Iterable<E> iterable) {
var copy = new ArrayList<E>();
iterable.forEach(copy::add);
return copy;
}
}
`SimpleFluentIterable`はeagerに評価され、現実世界のアプリケーションにはコストが高すぎます。
public class SimpleFluentIterable<E> implements FluentIterable<E> {
// ...
}
`LazyFluentIterable`は終了時に評価されます。
public class LazyFluentIterable<E> implements FluentIterable<E> {
// ...
}
その使用方法については、フィルタリング、変換、収集された単純な数値リストを使用して説明します。結果はその後出力されます。
public static void main(String[] args) {
var integerList = List.of(1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68);
prettyPrint("The initial list contains: ", integerList);
var firstFiveNegatives = SimpleFluentIterable
.fromCopyOf(integerList)
.filter(negatives())
.first(3)
.asList();
prettyPrint("The first three negative values are: ", firstFiveNegatives);
var lastTwoPositives = SimpleFluentIterable
.fromCopyOf(integerList)
.filter(positives())
.last(2)
.asList();
prettyPrint("The last two positive values are: ", lastTwoPositives);
SimpleFluentIterable
.fromCopyOf(integerList)
.filter(number -> number % 2 == 0)
.first()
.ifPresent(evenNumber -> LOGGER.info("The first even number is: {}", evenNumber));
var transformedList = SimpleFluentIterable
.fromCopyOf(integerList)
.filter(negatives())
.map(transformToString())
.asList();
prettyPrint("A string-mapped list of negative numbers contains: ", transformedList);
var lastTwoOfFirstFourStringMapped = LazyFluentIterable
.from(integerList)
.filter(positives())
.first(4)
.last(2)
.map(number -> "String[" + number + "]")
.asList();
prettyPrint("The lazy list contains the last two of the first four positive numbers "
+ "mapped to Strings: ", lastTwoOfFirstFourStringMapped);
LazyFluentIterable
.from(integerList)
.filter(negatives())
.first(2)
.last()
.ifPresent(number -> LOGGER.info("Last amongst first two negatives: {}", number));
}
プログラム出力
08:50:08.260 [main] INFO com.iluwatar.fluentinterface.app.App -- The initial list contains: 1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68.
08:50:08.265 [main] INFO com.iluwatar.fluentinterface.app.App -- The first three negative values are: -61, -22, -87.
08:50:08.265 [main] INFO com.iluwatar.fluentinterface.app.App -- The last two positive values are: 23, 2.
08:50:08.266 [main] INFO com.iluwatar.fluentinterface.app.App -- The first even number is: 14
08:50:08.267 [main] INFO com.iluwatar.fluentinterface.app.App -- A string-mapped list of negative numbers contains: String[-61], String[-22], String[-87], String[-82], String[-98], String[-68].
08:50:08.270 [main] INFO com.iluwatar.fluentinterface.app.App -- The lazy list contains the last two of the first four positive numbers mapped to Strings: String[18], String[6].
08:50:08.270 [main] INFO com.iluwatar.fluentinterface.app.App -- Last amongst first two negatives: -22
JavaでFluent Interfaceパターンを使用する場面
JavaでFluent Interfaceパターンを使用する状況
- 頻繁に使用され、クライアントコードの可読性が非常に重要なAPIを設計する場合。
- 複雑なオブジェクトを段階的に構築し、コードをより直感的でエラーが発生しにくいものにする必要がある場合。
- 特に構成やオブジェクト構築のシナリオにおいて、コードの明確性を高め、冗長なコードを削減する場合。
Fluent InterfaceパターンJavaチュートリアル
JavaにおけるFluent Interfaceパターンの実用例
- Java 8 Stream API
- Google Guava FluentIterable
- JOOQ
- Mockito
- Java Hamcrest
- 統合ワークフローのためのApache Camelなどのライブラリにおけるビルダー。
Fluent Interfaceパターンのメリットとデメリット
メリット
- JavaプロジェクトでFluent Interfaceパターンを採用すると、コードの可読性と保守性を大幅に向上させることができます。
- メソッドは通常新しいインスタンスを返すため、不変オブジェクトの構築を促進します。
- コンテキストはチェーン内で維持されるため、変数の必要性が減少します。
デメリット
- このパターンに慣れていない人には、直感的なコードにならない可能性があります。
- メソッド呼び出しの連鎖のため、デバッグが困難になる可能性があります。
- 過剰に使用すると、複雑で保守困難なコード構造につながる可能性があります。
関連するJavaデザインパターン
- ビルダー:オブジェクトを段階的に構築するために、Fluent Interfaceを使用して実装されることが多いです。ビルダーパターンは複雑なオブジェクトの構築に焦点を当てている一方、Fluent Interfaceはメソッドチェーンメカニズムを強調しています。
- 責任連鎖:Fluent Interfaceは、チェーン内の各メソッドがタスクの一部を処理し、次のメソッドに委譲するという責任連鎖の特定の利用方法と見なすことができます。