其中最强大的一处是,它可以针对 Twitter 支持的其他任何格式进行扩展 — fromXml 方法可以在分解节点之前检查它是否保存了 XML、RSS 或 Atom 类型的内容,或者 Status 可以包含 fromXml、fromRss、fromAtom 和 fromJson 方法。实际上,后一种方法是我的优先选择,因为它会平等对待基于 XML 的格式和 JSON(基于文本)格式。
好奇和细心的读者会注意到在 Status 及其内嵌 User 的 fromXml 方法中,我使用的是 XPath 样式的分解方法,而不是之前建议的遍历内嵌元素的方法。现在,XPath 样式的方法看上去更易于阅读,但幸运的是,我后来改变了注意,良好的封装仍然是我的朋友 — 我可以在随后修改它,Scitter 外部的任何人都不会知道。
注意 Status 内部的两个成员如何使用 Option[T] 类型;这是因为这些元素通常排除在 Status 消息外部,并且虽然元素本身会出现,但它们显示为空(类似于 <in_reply_to_user_id></in_reply_to_user_id>)。这正是 Option[T] 的作用所在。当元素为空时,它们将使用 “None” 值。(这表示考虑到基于 Java 的兼容性,访问它们会更加困难,但惟一可行方法是对最终生成的 Option 实例调用 get(),这不太复杂并且能很好地解决 “非 null 即 0” 问题)。
现在已经可以轻而易举地使用公共时间轴:
清单 14. 分解公共时间轴
@Test def simplePublicFeedPullAndDeserialize = { val publicFeedURL = "http://twitter.com/statuses/public_timeline.xml" // HttpClient API 101 val client = new HttpClient() val method = new GetMethod(publicFeedURL) method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false)) val statusCode = client.executeMethod(method) val responseBody = new String(method.getResponseBody()) val responseXML = scala.xml.XML.loadString(responseBody) val statuses = responseXML \\ "status" for (n <- statuses.elements) { val s = Status.fromXml(n) System.out.println("\t'@" + s.user.screenName + "' wrote " + s.text) } } |
显然,这看上去更加简洁,并且易于使用。
将所有这些结合到 Scitter 单一实例中相当简单,仅涉及执行查询、解析各个 Status 元素以及将它们添加到 List[Status] 实例中,如清单 15 所示:
清单 15. Scitter.publicTimeline
package com.tedneward.scitter { import org.apache.commons.httpclient._, auth._, methods._, params._ import scala.xml._ object Scitter { // ... /** * Query the public timeline for the most recent statuses. * * Twitter docs say: * public_timeline * Returns the 20 most recent statuses from non-protected users who have set * a custom user icon. Does not require authentication. Note that the * public timeline is cached for 60 seconds so requesting it more often than * that is a waste of resources. * URL: http://twitter.com/statuses/public_timeline.format * Formats: xml, json, rss, atom * Method(s): GET * API limit: Not applicable * Returns: list of status elements */ def publicTimeline : List[Status] = { import scala.collection.mutable.ListBuffer val client = new HttpClient() val method = new GetMethod("http://twitter.com/statuses/public_timeline.xml") method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false)) client.executeMethod(method) val statusLine = method.getStatusLine() if (statusLine.getStatusCode() == 200) { val responseXML = XML.loadString(method.getResponseBodyAsString()) val statusListBuffer = new ListBuffer[Status] for (n <- (responseXML \\ "status").elements) statusListBuffer += (Status.fromXml(n)) statusListBuffer.toList } else { Nil } } } } |
在实现功能全面的 Twiter 客户机之前,我们显然还有很长的路要走。但到目前为止,我们已经实现基本的行为。
结束语
构建 Scitter 库的工作进展顺利;目前,Scitter 测试实现相对比较简单,与产生 Scitter API 的探索测试相比时尤为如此。外部用户不需要担心 Twitter API 或者它的各种格式的复杂性,虽然目前测试 Scitter 库有点困难(对单元测试而言,依赖网络并不是个好方法),但我们会及时解决此问题。
注意,我故意在 Twitter API 中维持了面向对象的感觉,秉承了 Scala 的精神 — 因为 Scala 支持大量功能特性并不表示我们要放弃 Java 结构采用的对象设计方法。我们将接受有用的功能特性,同时仍然保留适用的 “旧方法”。
这并不是说我们在此处提供的设计是解决问题最好的方法,只能说这是我们决定采用的设计方法;并且,因为我是本文的作者,所以我采用的是自己的方式。如果不喜欢,您可以编写自己的库和文章(并将 URL 发送给我,我会在未来的文章中向您发起挑战)。事实上,在未来的文章中,我会将所有这些封装在一个 Scala “sbaz” 包中,并上传到网上供大家下载。
现在,我们又要暂时说再见了。下个月,我将在 Scitter 库中添加更多有趣的特性,并开始考虑如何简化它的测试和使用。(责任编辑:A6)