Fluent Interface

---转载自http://huidian.iteye.com/blog/426664

翻译自大师martin fowler 05年的博客文章,虽然时效性太差,不过许多开源项目中都用到了这种接口的设计风格,权当学习吧。 
原文地址http://martinfowler.com/bliki/FluentInterface.html
 
    
        几个月前,我同Eric Evans参加一个工作讨论组,Eric谈到某种接口风格,我们决定将它命名为Fluent Interface(连贯接口)。这不是个通用风格,但我们认为应该值得认识。可能认识它的最好方式是通过例子。 
        一个最简单的例子可能是Eric的timeAndMoney库。在一般情况下,为获得时间间隔段,我们可能会看到如下代码: 
  

Java代码   收藏代码
  1. TimePoint fiveOClock, sixOClock;  
  2. ...  
  3. TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);  


   timeAndMoney库的使用者可通过下面方式使用该库: 
  

Java代码   收藏代码
  1. TimeInterval meetingTime = fiveOClock.until(sixOClock);  
  2.      


        我将继续一个更为通用的,顾客填写订单的例子。订单是一列列的关于数量与产品的条目,其中有一列可以跳过,这意味着,我宁愿这列不填写,也得让不能延误整个订单的投递。因此我需要给予整个订单一个rush状态。
        我看到的大部分对该类情况的代码像下面这样: 
  

Java代码   收藏代码
  1. private void makeNormal(Customer customer) {  
  2.         Order o1 = new Order();  
  3.         customer.addOrder(o1);  
  4.         OrderLine line1 = new OrderLine(6, Product.find("TAL"));  
  5.         o1.addLine(line1);  
  6.         OrderLine line2 = new OrderLine(5, Product.find("HPK"));  
  7.         o1.addLine(line2);  
  8.         OrderLine line3 = new OrderLine(3, Product.find("LGV"));  
  9.         o1.addLine(line3);  
  10.         line2.setSkippable(true);  
  11.         o1.setRush(true);  
  12.     }  


        大体上是创建了不同的对象并把它们装配起来,如果不能通过构造函数的方式来添加,那么我们需要使用临时的变量来完成装载。(这里是利用了集合) 
    下面是利用Fluent(连贯)风格来设计接口的代码: 
    

Java代码   收藏代码
  1.  private void makeFluent(Customer customer) {  
  2.     customer.newOrder()  
  3.             .with(6"TAL")  
  4.             .with(5"HPK").skippable()  
  5.             .with(3"LGV")  
  6.             .priorityRush();  
  7. }  


        可能值得重视的是,该风格类似一种内部"DomainSpecificLanguage(DSL领域特定语言)",这也是我们为何用"Fluent(连贯)"来描述它。很多情况下,这两者是同义词。API设计的最基本要求是可读性和流畅性,因此花费更多精力去思考API结构本身让它更加连贯是有价值的。简单的如一些构造器,setter以及单纯的添加方法是很容易写出,而提出一个优秀的Fluent(连贯)的API则需要一些思考。 
        正当我在Calgary一家咖啡厅匆忙的完成早餐时,确信这种风格存在一个问题(个人理解,或许是作者是想表达因为赶早餐而有感而发),好的Fluent(连贯)API需要花费一些时间来构建。如果你需要更多类似例子,可以参考http://www.jmock.org/(JMOCK),JMOCK和其他的MOCK库一样,需要创建复杂的行为规范。近些年已经构建了很多mocking库,而JMOCK包含了一些让程序十分流畅的,相当优美的Fluent(连贯)API。下面是一个例子: 
    

Java代码   收藏代码
  1. mock.expects(once()).method("m").with( or(stringContains("hello"),  
  2.                                           stringContains("howdy")) );  
  3.   
  4.       


         我注意到,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)。

猜你喜欢

转载自fratemity.iteye.com/blog/1997620