每周源代码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
“无论流畅接口是不是DSL的一种形式,它显然是一种流畅接口形式。”——Scott Bellware
你也在研究流畅接口吗?
--------------------------------------------------------------
不好意思插一句:我还想提一下我们的小论坛:https://hanselman.com/forum。那是在很棒的JitBit的AspNetForum应用上运行的,我看来最小的论坛。在那里会讨论很多多样化的有趣的话题,你可以看下最近的博文,每一个页面都有RSS 提要。