式ビルダー

2013年8月8日

FluentInterfaceの問題の1つは、奇妙に見えるメソッドを使用することです。次の例を考えてみてください。

customer.newOrder()
  .with(6, "TAL")
  .with(5, "HPK").skippable()
  .with(3, "LGV")
  .priorityRush();

withskippablepriorityRushなどのメソッドは、Orderクラスには適していません。ネーミングは、Fluentインターフェイスが提供する小さなドメイン固有言語のコンテキストでは機能しますが、通常、コマンドクエリAPIと呼ばれるフォームでAPIを期待します。コマンドクエリAPIでは、各メソッドは任意のコンテキストで個別に意味を成します。また、コマンドクエリ分離などの原則に従う場合もあります。これは、JavaではオブジェクトのObservableStateを変更するメソッドは戻り値を持つべきではないことを意味します。Fluentスタイルのメソッドとコマンドクエリメソッドを混在させると、FluentメソッドがほとんどのAPIに期待されるものとの期待に反することがあるため、混乱が生じる可能性があります。

式ビルダーはこの問題に対する解決策です。式ビルダーは、Fluentインターフェイスを定義する別個のオブジェクトで、Fluent呼び出しを基になる通常のAPI呼び出しに変換します。したがって、注文の場合の式ビルダーは次のようなものになります。

public class OrderBuilder {
  private Order subject = new Order();
  private OrderLine currentLine;

  public OrderBuilder with(int quantity, String productCode) {
    currentLine = new OrderLine(quantity, Product.find(productCode));
    subject.addLine(currentLine);
    return this;
  }

  public OrderBuilder skippable() {
    currentLine.setSkippable(true);
    return this;
  }

  public OrderBuilder priorityRush() {
    subject.setRush(true);
    return this;
  }

  public Order getSubject() {
    return subject;
  }
}

この場合、単一の式ビルダークラスがありますが、顧客ビルダー、注文ビルダー、行ビルダーなどの小規模なビルダー構造にすることもできます。1つのオブジェクトを使用する場合、skippableメソッドで現在処理している行を追跡するための変数が必要になります。構造を使用するとこれを回避できますが、少し複雑になり、低レベルのビルダーが上位のビルダーを対象としたメソッドを処理できることを確認する必要があります。この場合、OrderLineBuilderには、OrderBuilderのすべてのメソッドにデリゲートするメソッドが必要です。

関連情報

式ビルダーの詳細については、ドメイン固有言語に関する私の本で説明しています。

式ビルダーを使用する優れたオープンな例として、JMockライブラリがあります。OOPSLA論文では、JMockのDSLハンドリングの進化について非常にわかりやすく説明しています。

改訂履歴

2007年1月4日に最初に公開されました。2013年8月8日に改訂されました。