抽象地讨论 Scala 是一件有趣的事情但对于本专栏的大多数读者而言需要通过实践才能理解理论和应用之间的区别在本期文章中Ted Neward 将使用 Scala 为客户构建基础框架用于访问流行的微型博客系统 Twitter
Twitter 迅速占领了 Internet 市场您肯定知道这个出色的社交网络工具允许订阅者提供关于他们自身以及当前正在执行的任务的简要状态更新追随者将接收到他们的 Twitter 提要 的更新这与博客将更新生成到博客阅读者的提要中极为类似
关于本系列
Ted Neward 将深入探讨 Scala 编程语言并带领您一路随行在本 系列 中您将学习最新的热点以及 Scala 的语言功能Scala 代码和 Java; 代码将在必要时同时出现以方便进行比较但您会发现 Scala 中的许多内容都与 Java 没有直接关系 — 这便是 Scala 的魄力所在!毕竟如果 Java 能够做到为什么还要大费周折来学习 Scala 呢?
就其本身而言Twitter 是对社交网络的有趣讨论并且是用户之间的新一代 高度互联它具备您能想到的所有优点和缺点
由于 Twitter 很早就发布了其 API因此大量 Twitter 客户机应用程序涌入到 Internet 上由于该 API 主要建立在直观和易于理解的基础上因此许多开发人员都发现有必要构建一个自己的 Twitter 客户机这与学习 Web 技术的开发人员构建自己的博客服务器极为类似
考虑到 Scala 的功能性(这看上去能很好地协同 Twitter 的 REST 式特性)以及非常出众的 XML 处理特性因此尝试构建一个用于访问 Twitter 的 Scala 客户机库应该是一个非常不错的体验
何为 Twitter?
在详细讨论之前我们先来看看 Twitter API
简单来说Twitter 是一个 微型博客 — 关于您自己的简短个性化提要不超过 个字符任何 追随者 都可以通过 Web 更新RSS文本消息等方式接收它们( 字符的限制完全来自文本消息它是 Twitter 的主要来源渠道并受到类似的限制)
最具 REST 特征 是什么意思?
一些读者会对我所使用的最具 REST 特征 短语感到好奇;这需要一些说明Twitter API 试图符合 Representational State Transfer (REST) 的设计原则并且在很大程度上说它做到了该思想的创造者 Roy Fielding 可能不同意 Twitter 使用这个术语但实现来说Twitter 的方法将适合大多数人的 REST 定义我只希望避免关于 REST 定义的激烈争论因此我使用了限定词 最
从实际的角度来说Twitter 是一个最具 REST 特征 的 API您可以使用一些种类的消息格式 — XMLATOMRSS 或 JSON — 来发送或从 Twitter 服务器接收消息不同的 URL与不同的消息和它们所需及可选的消息部分相结合可以发起不同的 API 调用例如如果您希望接收 Twitter 上所有人的所有 Tweets(Twitter 更新)的完整列表(也称作 公共时间轴)您需要准备一个 XMLATOMRSS 或 JSON 消息将它发送给合适的 URL并采用与 Twitter 网站()上相同的格式来使用结果
public_timeline
返回设定了自定义用户图标的
非保护用户的 条最新状态不需要身份验证
注意公共时间轴将缓存 秒钟
因此频繁请求它不再浪费资源
URL _timelineformat
格式xmljsonrssatom
方法GET
API 限制不适用
返回状态元素列表
从编程的角度来说这意味着我们给 Twitter 服务器发送一个简单的 GET HTTP 请求并且我们将获取一组封装在 XMLRSSATOM 或 JSON 消息中的 状态 消息Twitter 站点将 状态 消息定义为类似清单 所示的内容
清单 您好世界您在哪里?
<feedxml:lang=enUSxmlns=>
<title>Twitter/tedneward</title>
<id>tag::Status</id>
<linktype=text/htmlrel=alternate/>
<updated>T::+:</updated>
<subtitle>TwitterupdatesfromTedNeward/tedneward</subtitle>
<entry>
<title>tedneward:@kdellisonHappenstothebestofus</title>
<contenttype=html>tedneward:@kdellisonHappenstothebestofus</content>
<id>tag::;/id>
<published>T::+:</published>
<updated>T::+:</updated>
<linktype=text/htmlrel=alternate
/>
<linktype=image/pngrel=image
_production/profile_images/
/javapolis_normalpng/>
<author>
<name>TedNeward</name>
<uri>;/uri>
</author>
</entry>
</feed>
部的话)都很直观因此不再赘述
由于我们可以采用三种基于 XML 的格式使用 Twitter 消息以及 Scala 具备一些非常强大的 XML 特性包括 XML 字面值和类似 XPath 的查询语法 API因此编写可以发送和接收 Twitter 消息的 Scala 库只需要一些基础的 Scala 编码工作举例来说通过 Scala 使用清单 消息来提取状态更新的标题或内容可以利用 Scala 的 XML 类型和 \ 及 \\ 方法如清单 所示
清单 您好 Ted您在哪里?
<![CDATA[
packagecomtednewardscittertest
{
classScitterTest
{
importorgjunit_Assert_
@TestdefsimpleAtomParse=
{
valatom=
<feedxml:lang=enUSxmlns=>
<title>Twitter/tedneward</title>
<id>tag::Status</id>
<linktype=text/htmlrel=alternate/>
<updated>T::+:</updated>
<subtitle>TwitterupdatesfromTedNeward/tedneward</subtitle>
<entry>
<title>tedneward:@kdellisonHappenstothebestofus</title>
<contenttype=html>tedneward:@kdellison
Happenstothebestofus</content>
<id>tag::
;/id>
<published>T::+:</published>
<updated>T::+:</updated>
<linktype=text/htmlrel=alternate
/>
<linktype=image/pngrel=image
_production/profile_images/
/javapolis_normalpng/>
<author>
<name>TedNeward</name>
<uri>;/uri>
</author>
</entry>
</feed>
assertEquals(atom\\entry\title
tedneward:@kdellisonHappenstothebestofus)
}
}
}
]]>
有关 Scala 的 XML 支持的更多详细信息请参阅 Scala 和 XML(参见 参考资料)
实际上使用原始 XML 本身并不是一个有趣的练习如果 Scala 的宗旨是让我们的生活更加轻松那么可以创建一个或一组专用于简化 Scala 消息发送和接收任务的类作为其中一个目标应该能够在 普通 Java 程序中方便地使用库(这意味着可以方便地从任何可理解普通 Java 语义的环境中来访问它比如说 Groovy 或 Clojure)
API 设计
在深入了解 Scala/Twitter 库的 API 设计之前(根据同事 ThoughtWorker Neal Ford 的建议我将它称作 Scitter)需要明确一些需求
首先Scitter 显然会对网络访问有一些依赖 — 并且可扩展到 Twitter 服务器 — 这会使测试变得非常困难
其次我们需要解析(和测试)Twitter 发回的各种格式
第三我们希望隐藏 API 内部各种格式之间的差异以便客户机不需要担心已记录的 Twitter 消息格式但是可以仅使用标准类
最后由于 Twitter 依赖 通过身份验证的用户 才能使用大量 API因此 Scitter 库需要适应 验证 和 未验证 API 之间的差异而不会让事情变得过于复杂
网络访问需要一些形式的 HTTP 通信以便联系 Twitter 服务器虽然我们可以使用 Java 库本身(特别是 URL 类及其同胞)但由于 Twitter API 需要大量请求和响应主体连接因此可以更加轻松地使用不同的 HTTP API特别是 Apache Commons HttpClient 库为了更便于测试客户机 API实际通信将隐藏在一些 API 内部的 Scitter 库中以便能够更加轻松地切换到另一个 HTTP 库(其必要性不太容易想到)并能模拟实际网络通信以简化测试(其作用很容易想到)
结果第一个测试是 Scala 化 HttpClient 调用以确保基本通信模式就位;注意由于 HttpClient 依赖另外两个 Apache 库(Commons Logging 和 Commons Codec)因此还需要在运行时提供这些库;对于那些希望开发相似种类代码的读者确保类路径中包括所有三个库
由于最易于使用的 Twitter API 是测试 API
因此在请求格式中返回 ok并附带 OK HTTP 状态码
我们将使用它作为 Scitter 测试中的保留条款它位于 URL (其中format 为 xml 或 json;至于目前我们将选择使用 xml)并且仅有的支持 HTTP 方法是 GETHttpClient 代码简明易懂如清单 所示
清单 Twitter PING!
packagecomtednewardscittertest
{
classExplorationTests
{
//
importmons_methods_params_cookie_
@TestdefcallTwitterTest=
{
valtestURL=
//HttpClientAPI
valclient=newHttpClient()
valmethod=newGetMethod(testURL)
methodgetParams()setParameter(HttpMethodParamsRETRY_HANDLER
newDefaultHttpMethodRetryHandler(false))
clientexecuteMethod(method)
valstatusLine=methodgetStatusLine()
assertEquals(statusLinegetStatusCode())
assertEquals(statusLinegetReasonPhrase()OK)
}
}
}
此代码中最重要的一部分是 HttpClient 样板 — 感兴趣的读者应该查阅 HttpClient API 文档了解详细信息假设连接到公共 Internet 的网络可用(并且 Twitter 并未修改其公共 API)那么该测试应该能顺利通过
鑒于此我们详细分析 Scitter 客户机的第一部分这意味着我们需要解决一个设计问题如何构建 Scitter 客户机来处理经过验证和未经过验证的调用目前我将采用典型的 Scala 方式假定验证是 按对象 执行的因此将需要验证的调用放在类定义中并在未验证的调用放在对象定义中
清单 Scittertest
packagecomtednewardscitter
{
/**
*ObjectforconsumingnonspecificTwitterfeedssuchasthepublictimeline
*UsethistodononauthenticatedrequestsofTwitterfeeds
*/
objectScitter
{
importmons_methods_params_cookie_
/**
*Pingtheservertoseeifitsupandrunning
*
*Twitterdocssay:
*test
*ReturnsthestringokintherequestedformatwithaOKHTTPstatuscode
*URL:
*Formats:xmljson
*Method(s):GET
*/
deftest:Boolean=
{
valclient=newHttpClient()
valmethod=newGetMethod()
methodgetParams()setParameter(HttpMethodParamsRETRY_HANDLER
newDefaultHttpMethodRetryHandler(false))
clientexecuteMethod(method)
valstatusLine=methodgetStatusLine()
statusLinegetStatusCode()==
}
}
/**
*ClassforconsumingauthenticateduserTwitterAPIsEachinstanceis
*thustiedtoaparticularauthenticateduseronTwitterandwill
*behaveaccordingly(accordingtotheTwitterAPIdocumentation)
*/
classScitter(username:Stringpassword:String)
{
}
}
目前我们将网络抽象放在一边 — 稍后当离线测试变得更加重要时再添加它当我们更好地理解如何使用 HttpClient 类时这还将帮助避免 过度抽象 网络通信
由于已经明确区分了验证和未验证 Twitter 客户机因此我们将快速创建一个经过验证的方法看上去 Twitter 提供了一个可验证用户登录凭证的 API再次HttpClient 代码将类似于之前的代码除了将用户名和密码传递到 Twitter API 中之外
这引出了 Twitter 如何验证用户的概念快速查看 Twitter API 页面后可以发现 Twitter 使用的是一种 Stock HTTP 验证方法这与任何经过验证的资源在 HTTP 中的方法相同这意味着 HttpClient 代码必须提供用户名和密码作为 HTTP 请求的一部分而不是作为 POST 的主体如清单 所示
清单 您好 Twitter是我!
packagecomtednewardscittertest
{
classExplorationTests
{
deftestUser=TwitterUser
deftestPassword=TwitterPassword
@TestdefverifyCreds=
{
valclient=newHttpClient()
valverifyCredsURL=_credentialsxml
valmethod=newGetMethod(verifyCredsURL)
methodgetParams()setParameter(HttpMethodParamsRETRY_HANDLER
newDefaultHttpMethodRetryHandler(false))
clientgetParams()setAuthenticationPreemptive(true)
valdefaultcreds=newUsernamePasswordCredentials(testUsertestPassword)
clientgetState()setCredentials(newAuthScope(
AuthScopeANY_REALM)defaultcreds)
clientexecuteMethod(method)
valstatusLine=methodgetStatusLine()
assertEquals(statusLinegetStatusCode())
assertEquals(OKstatusLinegetReasonPhrase())
}
}
}
注意要让此测试顺利通信用户名和密码字段将需要输入 Twitter 能接收的内容 — 我在开发时使用了自己的 Twitter 用户名和密码但显然您需要使用自己设定的用户名和密码注册新的 Twitter 帐户相当简单因此我假定您已经拥有一个帐户或者知道如何注册(很好我会等待完成此任务)
完成后使用用户名和密码构造函数参数将它映射到 Scitter 类非常简单如清单 所示
清单 ScitterverifyCredentials
packagecomtednewardscitter
{
importmons_auth_methods_params_
//
/**
*ClassforconsumingauthenticateduserTwitterAPIsEachinstanceis
*thustiedtoaparticularauthenticateduseronTwitterandwill
*behaveaccordingly(accordingtotheTwitterAPIdocumentation)
*/
classScitter(username:Stringpassword:String)
{
/**
*VerifytheusercredentialsagainstTwitter
*
*Twitterdocssay:
*verify_credentials
*ReturnsanHTTPOKresponsecodeandarepresentationofthe
*requestinguserifauthenticationwassuccessful;returnsastatus
*codeandanerrormessageifnotUsethismethodtotestifsupplied
*usercredentialsarevalid
*URL:_credentialsformat
*Formats:xmljson
*Method(s):GET
*/
defverifyCredentials:Boolean=
{
valclient=newHttpClient()
valmethod=newGetMethod()
methodgetParams()setParameter(HttpMethodParamsRETRY_HANDLER
newDefaultHttpMethodRetryHandler(false))
clientgetParams()setAuthenticationPreemptive(true)
valcreds=newUsernamePasswordCredentials(usernamepassword)
clientgetState()setCredentials(
newAuthScope(AuthScopeANY_REALM)creds)
clientexecuteMethod(method)
valstatusLine=methodgetStatusLine()
statusLinegetStatusCode()==
}
}
}
清单 中相应的 Scitter 类测试也相当简单
清单 测试 ScitterverifyCredentials
packagecomtednewardscittertest
{
classScitterTests
{
importorgjunit_Assert_
importcomtednewardscitter_
deftestUser=TwitterUsername
deftestPassword=TwitterPassword
//
@TestdefverifyCreds=
{
valscitter=newScitter(testUsertestPassword)
valresult=scitterverifyCredentials
assertTrue(result)
}
}
}
不算太糟库的基本结构已经成形但显然还有很长的路要走特别是因为目前实际上未执行任何特定于 Scala 的任务 — 在面向对象设计中库的构建并不像练习那样简单因此我们开始使用一些 XML并通过更加合理的格式将它返回
从 XML 到对象
现在可以添加的最简单的 API 是 public_timeline它收集 Twitter 从所有用户处接收到的最新的 n 更新并返回它们以便于进行使用与之前讨论的另外两个 API 不同public_timeline API 返回一个响应主体(而不是仅依赖于状态码)因此我们需要分解生成的 XML/RSS/ATOM/然后将它们返回给 Scitter 客户机
现在我们编写一个探索测试它将访问公共提要并将结果转储到 stdout 以便进行分析如清单 所示
清单 大家都在忙什么?
packagecomtednewardscittertest
{
classExplorationTests
{
//
@TestdefcallTwitterPublicTimeline=
{
valpublicFeedURL=_timelinexml
//HttpClientAPI
valclient=newHttpClient()
valmethod=newGetMethod(publicFeedURL)
methodgetParams()setParameter(HttpMethodParamsRETRY_HANDLER
newDefaultHttpMethodRetryHandler(false))
clientexecuteMethod(method)
valstatusLine=methodgetStatusLine()
assertEquals(statusLinegetStatusCode())
assertEquals(statusLinegetReasonPhrase()OK)
valresponseBody=methodgetResponseBodyAsString()
Systemoutprintln(callTwitterPublicTimelinegot)
Systemoutprintln(responseBody)
}
}
}
运行后结果每次都会有所不同因为公共 Twitter 服务器上有许多用户但通常应与清单 的 JUnit 文本文件转储类似
清单 我们的 Tweets 结果
<statusestype=array>
<status>
<created_at>TueMar::+</created_at>
<id></id>
<text>Shereallyis;/text>
<source><a>twitterrific</a>
</source>
<truncated>false</truncated>
<in_reply_to_status_id></in_reply_to_status_id>
<in_reply_to_user_id></in_reply_to_user_id>
<favorited>false</favorited>
<user>
<id></id>
<name>Brittanie</name>
<screen_name>brittaniemarie</screen_name>
<description>ImabrightcharacterIsuppose</description>
<location>AtlantaorPhilly</location>
<profile_image_url>_production/profile_images/
/goodish_normaljpg</profile_image_url>
<url>;/url>
<protected>false</protected>
<followers_count></followers_count>
</user>
</status>
<status>
<created_at>TueMar::+</created_at>
<id></id>
<text>NumberofmyfourlifeprinciplesLifeisfunandrewarding</text>
<source>web</source>
<truncated>false</truncated>
<in_reply_to_status_id></in_reply_to_status_id>
<in_reply_to_user_id></in_reply_to_user_id>
<favorited>false</favorited>
<user>
<id></id>
<name>DaleGreenwood</name>
<screen_name>Greeendale</screen_name>
<description>VegetarianEatanduseonlyorganics
Lovehelpingpeoplebecomeprosperous</description>
<location>MelbourneAustralia</location>
<profile_image_url>_production/profile_images/
/Dock_normaljpg</profile_image_url>
<url>;/url>
<protected>false</protected>
<followers_count></followers_count>
</user>
</status>
(Alotmorehavebeensnipped)
</statuses>
通过查看结果和 Twitter 文档可以看出调用的结果是一组具备一致消息结构的简单 状态 消息使用 Scala 的 XML 支持分离结果相当简单但我们会在基本测试通过后立即简化它们如清单 所示
清单 大家都在忙什么?
packagecomtednewardscittertest
{
classExplorationTests
{
//
@TestdefsimplePublicFeedPullAndParse=
{
valpublicFeedURL=_timelinexml
//HttpClientAPI
valclient=newHttpClient()
valmethod=newGetMethod(publicFeedURL)
methodgetParams()setParameter(HttpMethodParamsRETRY_HANDLER
newDefaultHttpMethodRetryHandler(false))
valstatusCode=clientexecuteMethod(method)
valresponseBody=newString(methodgetResponseBody())
valresponseXML=scalaxmlXMLloadString(responseBody)
valstatuses=responseXML\\status
for(n<statuseselements)
{
nmatch
{
case{contents@_*}=>
{
Systemoutprintln(Status:)
contentsforeach((c)=>
cmatch
{
case{t@_*}=>
Systemoutprintln(\tText:+ttexttrim)
case{contents@_*}=>
{
contentsforeach((c)=>
cmatch
{
case{u}=>
Systemoutprintln(\tUser:+utexttrim)
case_=>
()
}
)
}
case_=>
()
}
)
}
case_=>
()//orifyoupreferSystemoutprintln(Unrecognizedelement!)
}
}
}
}
}
随着示例代码模式的变化这并不值得推荐 — 这有点类似于 DOM依次导航到各个子元素提取文本然后导航到另一个节点我可以仅执行两个 XPath 样式的查询如清单 所示
清单 替代解析方法
for(n<statuseselements)
{
valtext=(n\\text)text
valscreenName=(n\\user\screen_name)text
}
这显然更加简短但它带来了两个基本问题
我们可以强制 Scala 的 XML 库针对每个元素或子元素遍历一次图其速度会随时间减慢
我们仍然需要直接处理 XML 消息的结构这是两个问题中最为重要的
也就是说这种方式不具备可伸缩性 — 假设我们最终对 Twitter 状态消息中的每个元素都感兴趣我们将需要分别从各状态中提取各个元素
这又造成了另一个与各格式本身相关的问题记住Twitter 可以使用四种不同的格式并且我们不希望 Scitter 客户机需要了解它们之间的任何差异因此 Scitter 需要一个能返回给客户机的中间结构以便未来使用如清单 所示
清单 Breaker您的状态是什么?
abstractclassStatus
{
valcreatedAt:String
valid:Long
valtext:String
valsource:String
valtruncated:Boolean
valinReplyToStatusId:Option[Long]
valinReplyToUserId:Option[Long]
valfavorited:Boolean
valuser:User
}
这与 User 方式相类似考虑到简洁性我就不再重复了注意User 子元素有一个有趣的问题 — 虽然存在 Twitter 用户类型但其中内嵌了一个可选的 最新状态状态消息还内嵌了一个用户对于这种情况为了帮助避免一些潜在的递归问题我选择创建一个嵌入在 Status 内部的 User 类型以反映所出现的 User 数据;反之亦然Status 也可以嵌入在 User 中这样可以明确避免该问题(至少在没发现问题之前这种方法是有效的)
现在创建了表示 Twitter 消息的对象类型之后我们可以遵循 XML 反序列化的公共 Scala 模式创建相应的对象定义其中包含一个 fromXml 方法用于将 XML 节点分离到对象实例中如清单 所示
清单 分解 XML
/**
*Objectwrapperfortransforming(format)intoStatusinstances
*/
objectStatus
{
deffromXml(node:scalaxmlNode):Status=
{
newStatus{
valcreatedAt=(node\created_at)text
valid=(node\id)texttoLong
valtext=(node\text)text
valsource=(node\source)text
valtruncated=(node\truncated)texttoBoolean
valinReplyToStatusId=
if((node\in_reply_to_status_id)text!=)
Some((node\in_reply_to_status_id)texttoLong)
else
None
valinReplyToUserId=
if((node\in_reply_to_user_id)text!=)
Some((node\in_reply_to_user_id)texttoLong)
else
None
valfavorited=(node\favorited)texttoBoolean
valuser=UserfromXml((node\user)())
}
}
}
其中最强大的一处是它可以针对 Twitter 支持的其他任何格式进行扩展 — fromXml 方法可以在分解节点之前检查它是否保存了 XMLRSS 或 Atom 类型的内容或者 Status 可以包含 fromXmlfromRssfromAtom 和 fromJson 方法实际上后一种方法是我的优先选择因为它会平等对待基于 XML 的格式和 JSON(基于文本)格式
好奇和细心的读者会注意到在 Status 及其内嵌 User 的 fromXml 方法中我使用的是 XPath 样式的分解方法而不是之前建议的遍历内嵌元素的方法现在XPath 样式的方法看上去更易于阅读但幸运的是我后来改变了注意良好的封装仍然是我的朋友 — 我可以在随后修改它Scitter 外部的任何人都不会知道
注意 Status 内部的两个成员如何使用 Option[T] 类型;这是因为这些元素通常排除在 Status 消息外部并且虽然元素本身会出现但它们显示为空(类似于 )这正是 Option[T] 的作用所在当元素为空时它们将使用 None 值(这表示考虑到基于 Java 的兼容性访问它们会更加困难但惟一可行方法是对最终生成的 Option 实例调用 get()这不太复杂并且能很好地解决 非 null 即 问题)
现在已经可以轻而易举地使用公共时间轴
清单 分解公共时间轴
@TestdefsimplePublicFeedPullAndDeserialize=
{
valpublicFeedURL=_timelinexml
//HttpClientAPI
valclient=newHttpClient()
valmethod=newGetMethod(publicFeedURL)
methodgetParams()setParameter(HttpMethodParamsRETRY_HANDLER
newDefaultHttpMethodRetryHandler(false))
valstatusCode=clientexecuteMethod(method)
valresponseBody=newString(methodgetResponseBody())
valresponseXML=scalaxmlXMLloadString(responseBody)
valstatuses=responseXML\\status
for(n<statuseselements)
{
vals=StatusfromXml(n)
Systemoutprintln(\t@+suserscreenName+wrote+stext)
}
}
显然这看上去更加简洁并且易于使用
将所有这些结合到 Scitter 单一实例中相当简单仅涉及执行查询解析各个 Status 元素以及将它们添加到 List[Status] 实例中如清单 所示
清单 ScitterpublicTimeline
packagecomtednewardscitter
{
importmons_auth_methods_params_
importscalaxml_
objectScitter
{
//
/**
*Querythepublictimelineforthemostrecentstatuses
*
*Twitterdocssay:
*public_timeline
*Returnsthemostrecentstatusesfromnonprotecteduserswhohaveset
*acustomusericonDoesnotrequireauthenticationNotethatthe
*publictimelineiscachedforsecondssorequestingitmoreoftenthan
*thatisawasteofresources
*URL:_timelineformat
*Formats:xmljsonrssatom
*Method(s):GET
*APIlimit:Notapplicable
*Returns:listofstatuselements
*/
defpublicTimeline:List[Status]=
{
importllectionmutableListBuffer
valclient=newHttpClient()
valmethod=
newGetMethod(_timelinexml)
methodgetParams()setParameter(HttpMethodParamsRETRY_HANDLER
newDefaultHttpMethodRetryHandler(false))
clientexecuteMethod(method)
valstatusLine=methodgetStatusLine()
if(statusLinegetStatusCode()==)
{
valresponseXML=
XMLloadString(methodgetResponseBodyAsString())
valstatusListBuffer=newListBuffer[Status]
for(n<(responseXML\\status)elements)
statusListBuffer+=(StatusfromXml(n))
statusListBuffertoList
}
else
{
Nil
}
}
}
}
在实现功能全面的 Twiter 客户机之前我们显然还有很长的路要走但到目前为止我们已经实现基本的行为
结束语
构建 Scitter 库的工作进展顺利;目前Scitter 测试实现相对比较简单与产生 Scitter API 的探索测试相比时尤为如此外部用户不需要担心 Twitter API 或者它的各种格式的复杂性虽然目前测试 Scitter 库有点困难(对单元测试而言依赖网络并不是个好方法)但我们会及时解决此问题
注意我故意在 Twitter API 中维持了面向对象的感觉秉承了 Scala 的精神 — 因为 Scala 支持大量功能特性并不表示我们要放弃 Java 结构采用的对象设计方法我们将接受有用的功能特性同时仍然保留适用的 旧方法
这并不是说我们在此处提供的设计是解决问题最好的方法只能说这是我们决定采用的设计方法;并且因为我是本文的作者所以我采用的是自己的方式如果不喜欢您可以编写自己的库和文章(并将 URL 发送给我我会在未来的文章中向您发起挑战)事实上在未来的文章中我会将所有这些封装在一个 Scala sbaz 包中并上传到网上供大家下载
现在我们又要暂时说再见了下个月我将在 Scitter 库中添加更多有趣的特性并开始考虑如何简化它的测试和使用