表達式建構器

2013 年 8 月 8 日

使用 FluentInterface 的問題之一是它會產生一些看起來很奇怪的方法。考慮這個範例

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

withskippablepriorityRush 這樣的函式在 Order 類別中並不恰當。命名在 fluent 介面提供的這個小 DomainSpecificLanguage 中運作良好,但我們通常預期 API 的形式是我所說的命令查詢 API。在命令查詢 API 中,每個函式在任何情境中都是個別有意義的。它也可能遵循一些原則,例如 CommandQuerySeparation,在 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;
  }
}

在這個案例中,我有一個單一的表達式建構器類別,但你也可以有一個小的建構器結構,例如客戶建構器、訂單建構器和明細建構器。使用單一物件表示你需要一個變數來追蹤你正在為 skippable 函式處理哪一行。使用結構可以避免這一點,但會稍微複雜一些,而且你需要確保較低層級的建構器可以處理預計用於較高層級建構器的函式。在這個案例中,OrderLineBuilder 需要為 OrderBuilder 的所有函式提供委派函式。

進一步閱讀

我在 Expression Builders 中更詳細地討論了 我的特定領域語言書籍

表達式建構函式的良好開放範例在 JMock 函式庫中。我發現 OOPSLA 論文 描述 JMock 的 DSL 處理演變非常有幫助。

修訂記錄

首次發布於 2007 年 1 月 4 日。2013 年 8 月 8 日修訂。