表达式构建器
2013年8月8日
使用流畅接口的一个问题是它会导致一些奇怪的方法。考虑以下示例
customer.newOrder() .with(6, "TAL") .with(5, "HPK").skippable() .with(3, "LGV") .priorityRush();
像with
、skippable
和priorityRush
这样的方法在Order类上看起来不太合适。这种命名方式在流畅接口提供的领域特定语言的上下文中效果很好,但我们通常期望API以我称之为命令-查询API的形式出现。在命令-查询API中,每个方法在任何上下文中都具有独立的意义。它还可以遵循诸如命令查询分离之类的原则,在Java中这意味着更改对象可观察状态的方法不应该有返回值。将流畅风格的方法与命令-查询方法混合可能会导致混淆,因为流畅方法可能会违反大多数API应该具有的外观期望。
表达式构建器是解决此问题的一种方法。表达式构建器是一个独立的对象,我们在其上定义流畅接口,然后将流畅调用转换为底层的常规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
方法的委托方法。
进一步阅读
我在表达式构建器方面进行了更详细的讨论,内容在我的关于领域特定语言的书中。
一个很好的、开放的表达式构建器使用示例是在JMock库中。我发现描述JMock的DSL处理演变的OOPSLA论文非常有帮助。
修订历史
首次发布日期:2007年1月4日。修订日期:2013年8月8日。