欢迎您光临本小站。希望您在这里可以找到自己想要的信息。。。

Fluent Interface(连贯接口)

架构&设计模式 water 2898℃ 0评论

 几个月前,我同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"),  

    stringContains("howdy")) );  

  2.   

  3.       


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

转载请注明:学时网 » Fluent Interface(连贯接口)

喜欢 (0)or分享 (0)

您必须 登录 才能发表评论!