每周源代码14 –流畅接口版

[原文发表地址] The Weekly Source Code 14 - Fluent Interface Edition

[原文发表时间] 2008-01-30 22:20

如果你是新来的,那我要告诉你我每周都会发表一些非常有趣的源代码片段(读起来有的漂亮,有的很丑,有的很聪明,也有很猥琐的)以及其项目出处。我之所以这么做是因为我深信阅读源代码和编写源一样重要,甚至更重要。我们靠阅读计算机书籍来让自己编出更好的程序,除此之外,你还得从其他一些开放源项目中寻求灵感,除非你已经在阅读像《编程珠玑》这样的书了。

所以,亲爱的读者,我在这里奉上第十四篇“每周源代码”,之后还会继续。这里是一些我这周在读的源。

在过去的一年中,我发现各国对所谓的“流畅接口”讨论得越来越多。由于C# 3.0扩展函数(mixins)的添加,导致了大家对接口的强烈兴趣,因为我们都希望编程和写散文一样简单

2005年,Martin Fowler在和Eric Evans一起参加的研讨会后谈及这个,那也是他们第一次将它们命名为“流畅接口”。他举了这样一个例子:

Eric的timeAndMoney库可能是最简单的一个例子了。用普通方式做一个时间间隔(time interval)我们看到的是这样的:

 TimePoint fiveOClock, sixOClock; 
  ... 
 TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock); 

而timeAndMoney库的用户则会这样做:

    TimeInterval meetingTime = fiveOClock.until(sixOClock); 

当然,常见的Ruby例子是这样的:

20.minutes.ago

Martin做了一个重大的突破,他试着在普通的OOP背景中加入“流畅”API,就像那些有着重的目标浏览器中的那样:

流畅接口函数的问题之一就是他们自身并没什么作用。用函数文档查看函数浏览器并没有多大意义。单独看来,我认为这个方法命名得并不好,没有很好地展示其原有意图。只有在流畅行动背景下它才能发挥优势。 在这种情况下,只有使用的生成器对象来解决问题了。—— Martin Fowler

紧接着,Piers Cawley为设计这些东西的人提供了一系列指导。欲见完整列表,请查阅他的博文

1. 隐藏你的工作

2. 请勿告诉他人你的进度

3. 认真考虑名字

4. 发挥你实现语言的优势

5. 如果你可以参考代码块的话,你会得到一些帮助。

6. 测试首份设计能很好地帮助你设计接口。

7. 设置合理的默认值

我认为,在.NET空间,Ayende的Rhino Mocks是在LINQ之前,语法完全正确得最早最好的例子。

Expect

.Call(mock.GetID(1))

.IgnoreArguments()

.Repeat

.Once()

.Return(something);

由于支持mixins,用Java也可以完成类似的操作。在Java5中这一操作称作静态导入

随着流畅接口的日益扩大和复杂化,Peirs指出他们应该叫做DSL(特定领域语言)。真正的DSL更简单,而且可能不那么流畅,但它可以自定义领域:

“似乎每当有人合理地用类方法、符号和哈希值写Ruby库时,他们总是被所得出的繁复结果所困惑,并将其称为调用域语言(或者嵌入式DSL).”

以下有两个很好的例子,演示了像DSL这样具有某些流畅特性的库,——是Why的Hpricot,一款为Ruby设的HTML解析器,如下所示:

 
    1: #!ruby
    2:  require 'hpricot'
    3:  require 'open-uri'
    4:  # load the RedHanded home page
    5:  doc = Hpricot(open("https://redhanded.hobix.com/index.html"))
    6:  # change the CSS class on links
    7:  (doc/"span.entryPermalink").set("class", "newLinks")
    8:  # remove the sidebar
    9:  (doc/"#sidebar").remove
   10:  # print the altered HTML
   11:  puts doc
 还有一个是Python的Beautiful Soup,同样也是一个HTML解析器。
 
    1: from BeautifulSoup import BeautifulSoup, Tag
    2:  soup = BeautifulSoup("Argh!FooBlah!")
    3:  tag = Tag(soup, "newTag", [("id", 1)])
    4:  tag.insert(0, "Hooray!")
    5:  soup.a.replaceWith(tag)
    6:  print soup
    7:  # Argh!Hooray!Blah!
 在C#部分,Garry Shutler使用扩展方法和lambada为如下MBUnit创建了更多的流畅断语:
    1: testObject.ShouldBeTheSameObjectAs(targetObject).And.ShouldBeEqualTo(testObject).And.ShouldSatisfy(x => x is Object);

但是DSL、流畅接口和API到底是什么呢?Martin在2006年补充到(在他继续写这他的DSL书时):

对我来说,一个关键点就是DSL在范围(它引用特定域)和能力(缺少通用语言的基本特征)上都受限。所以好的DSL一般都很小很简单:因此用“微语言”和“迷你语言”这样的术语来形容它们。

对于内部DSL来说,什么是API和什么是DSL之间界限模糊。他们基本没有区别,内部DSL就是有个花哨名字的API (就像贝尔实验室里流传的那句话一样:“设计库就是设计语言”)。尽管如此,我觉得当你使用用具有DSL风格的API时感觉还是不一样的。流畅接口可以给API运行带来质的改变。用DSL术语思考会让你从不一样的角度考虑可读性,运用宿主语言创建自主的东西——rake是一个很好的例子。—— Martin Fowler

即使是想PHP这样的脚本语言也跟上了流畅接口的步伐,假定这段内容中的“with”有意义。

    1: <?php
    2: private function makeFluent(Customer $customer) {
    3:     $customer->  newOrder()
    4:          ->with(6, 'TAL')
    5:          ->with(5, 'HPK')->skippable()
    6:          ->with(3, 'LGV')
    7:          ->priorityRush();           

最终我觉得Paul Jones抓到了重点,他说“流畅接口需要流畅的情况。 ”:

“我觉得,要让一个流畅接口有效,你得创造条件,可以一次性获取到所有的信息,从而将方法都流畅地连接起来。”——Paul M. Jones

Scott Bellware说:

“无论流畅接口是不是DSL的一种形式,它显然是一种流畅接口形式。”——Scott Bellware

你也在研究流畅接口吗?

--------------------------------------------------------------

不好意思插一句:我还想提一下我们的小论坛:https://hanselman.com/forum。那是在很棒的JitBit的AspNetForum应用上运行的,我看来最小的论坛。在那里会讨论很多多样化的有趣的话题,你可以看下最近的博文,每一个页面都有RSS 提要。