翻译自大师martin fowler 05年的博客文章,虽然时效性太差,不过许多开源项目中都用到了这种接口的设计风格,权当学习吧。
原文地址http://martinfowler.com/bliki/FluentInterface.html
几个月前,我同Eric Evans参加一个工作讨论组,Eric谈到某种接口风格,我们决定将它命名为Fluent Interface(连贯接口)。这不是个通用风格,但我们认为应该值得认识。可能认识它的最好方式是通过例子。
一个最简单的例子可能是Eric的timeAndMoney库。在一般情况下,为获得时间间隔段,我们可能会看到如下代码:
TimePoint fiveOClock, sixOClock;
...
TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);
timeAndMoney库的使用者可通过下面方式使用该库:
TimeInterval meetingTime = fiveOClock.until(sixOClock);
我将继续一个更为通用的,顾客填写订单的例子。订单是一列列的关于数量与产品的条目,其中有一列可以跳过,这意味着,我宁愿这列不填写,也得让不能延误整个订单的投递。因此我需要给予整个订单一个rush状态。
我看到的大部分对该类情况的代码像下面这样:
private void makeNormal(Customer customer) {
Order o1 = new Order();
customer.addOrder(o1);
OrderLine line1 = new OrderLine(6, Product.find("TAL"));
o1.addLine(line1);
OrderLine line2 = new OrderLine(5, Product.find("HPK"));
o1.addLine(line2);
OrderLine line3 = new OrderLine(3, Product.find("LGV"));
o1.addLine(line3);
line2.setSkippable(true);
o1.setRush(true);
}
大体上是创建了不同的对象并把它们装配起来,如果不能通过构造函数的方式来添加,那么我们需要使用临时的变量来完成装载。(这里是利用了集合)
下面是利用Fluent(连贯)风格来设计接口的代码:
private void makeFluent(Customer customer) {
customer.newOrder()
.with(6, "TAL")
.with(5, "HPK").skippable()
.with(3, "LGV")
.priorityRush();
}
可能值得重视的是,该风格类似一种内部"DomainSpecificLanguage(DSL领域特定语言)",这也是我们为何用"Fluent(连贯)"来描述它。很多情况下,这两者是同义词。API设计的最基本要求是可读性和流畅性,因此花费更多精力去思考API结构本身让它更加连贯是有价值的。简单的如一些构造器,setter以及单纯的添加方法是很容易写出,而提出一个优秀的Fluent(连贯)的API则需要一些思考。
正当我在Calgary一家咖啡厅匆忙的完成早餐时,确信这种风格存在一个问题(个人理解,或许是作者是想表达因为赶早餐而有感而发),好的Fluent(连贯)API需要花费一些时间来构建。如果你需要更多类似例子,可以参考
http://www.jmock.org/(JMOCK),JMOCK和其他的MOCK库一样,需要创建复杂的行为规范。近些年已经构建了很多mocking库,而JMOCK包含了一些让程序十分流畅的,相当优美的Fluent(连贯)API。下面是一个例子:
mock.expects(once()).method("m").with( or(stringContains("hello"),
stringContains("howdy")) );
我注意到,Steve Freeman和Nat Price关于JMOCK的API演变有一次非常成功的对话
(JAOO2005),交谈结果记录在
OOPSLA paper。
至此,我们通常看到Fluent(连贯)API被用来构建对象配置,且是值对象。尽管我怀疑这是一种共识,但不能确定这是否就是该类接口的特质。就本人而言,是否连贯的关键,在于作为DSL的特质。使用API时,越感觉行云流水,那么它就越连贯。
构建一个Fluent(连贯)API可能会出现一些不寻常的习惯。最明显的是setter方法将会返回一个值。(在订单的例子中,with这个添加一行订单信息的方法将返回这个订单)在传统的观点中,修改状态的方法一般返回为void,可以查看以下原则
Command Query Separation,这个观点与Fluent(连贯)API有所冲突,因此我倾向于在这种场景下,将先忽略这个习惯。
你需要选择一个返回值的类型,基于你设计如何让这个“动作连贯的流转下去”,JMOCK一个主要的观点是,返回类型依赖于你下一个动作需要什么类型。这种观点的好处在于,在IDE向导下,方法调用完成后,很容易知道下个调用类型。通常我发现,动态语言更适合于DSLs是因为它们的语法更加整洁。而使用这种Fluent(连贯)API,算是对静态语言的补充。
Fluent(连贯)API另外一个问题是,它无法很好的描述自身.察看方法名with,或许可以认为这个方法名不太合适,以至于无法很好的表达自身的意图.只是它仅仅在整个连贯动作的上下文语义中,所表达的意义才能被凸现.因此一个解决的途径,就是
Eric提及的他至今为止的使用情况, Fluent(连贯)接口大部分用于值对象配置。值对象没有领域特征,你能很容易的使用及传递它。因此可以在顺畅的流转过程中被重新赋值。
我没有看到大量的Fluent(连贯)API实例,因此我们无法对它的优缺点下结论。而现在还处于推广期,不管如何,我认为它已经准备就绪。
(08年6月更新)自从我写这篇blog来,它的使用范围越来越广泛了,这给予我很大的鼓舞。我《 fluent interfaces and internal DSLs 》中改进了我的观点。我也注意到一个普遍的错误观点——很多人认为Fluent(连贯)API等同于方法链(Method Chaining),确实链在Fluent(连贯)API中是一个应用得很普遍的技术,但是Fluent(连贯)API本身远远不只这些。
我看到JMock的例子中,不只用到方法链,还用到嵌套函数以及对象范围(object scoping)。
分享到:
相关推荐
在C#编程中,流畅接口(Fluent Interface)是一种设计模式,它使代码更易于阅读和理解,通过链式方法调用实现。这种接口风格通常用于构建领域特定语言(DSL),使得代码更接近自然语言,提高了代码的可读性和可维护...
在CodeIgniter中,连贯操作(Fluent Interface)是一种常见的编程技术,它允许开发者通过链式调用方法来构造复杂的对象状态或执行一系列操作。这种技术极大地提高了代码的可读性和简洁性。 连贯操作的底层原理主要...
表达式生成器提供了一组连贯接口,开发者可以通过这些接口构建出完整的查询表达式,然后再转换为底层的命令-查询API调用。这样的设计能够清晰地表达查询的意图,同时隐藏底层实现的复杂性。 例如,在一个数据库查询...
"流利的结果"这一标题可能指的是使用C#实现的一种编程模式,即流利接口(Fluent Interface)。这种设计模式使得代码更加可读、易于理解和维护,就像在读自然语言一样。 流利接口是通过方法链来实现的,每个方法返回...
3. **设计原则**:为了提供优秀的用户体验,开发者可能遵循了Microsoft的Fluent Design System,它提倡深度、动效、材质、光感和缩放等设计元素,以创建现代、连贯且吸引人的UI。 4. **用户反馈**:用户的好评意味...
对于那些寻找替代方案的开发者,有若干知名的设计系统可以选择,如Google的Material Design、Apple的Human Interface Guidelines、Ant Design、Fluent UI等。这些系统都经过了广泛的测试和优化,提供了丰富的组件库...