知识大全 面向Java开发人员的Scala指南: 增强Scitter库
Posted 知
篇首语:别在该厚脸皮的年纪里过度在意自尊,别在该努力的年纪怨天尤人。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 面向Java开发人员的Scala指南: 增强Scitter库相关的知识,希望对你有一定的参考价值。
面向Java开发人员的Scala指南: 增强Scitter库 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!
抽象地谈论 Scala 的确有趣 然而一旦将其付诸实践 就会发现将它作为 玩具 与在工作中使用它的区别 Scala 狂热者 Ted Neward 撰写了一篇 对 Scitter 的介绍 Scitter 是一个用于访问 Twitter 的 Scala 库 本文是其后续篇 在本文中 Ted Neward 为这个客户机库提供了一组更有趣也更有用的特性
欢迎回来 Scala 迷们 上个月 我们谈到了 Twitter 这个微博客站点目前正引起社会性网络的极大兴趣 我们还谈到它的基于 XML /REST 的 API 如何使它成为开发人员进行研究和探索的一个有趣平台 为此 我们首先充实了 Scitter 的基本结构 Scitter 是用于访问 Twitter 的一个 Scala 库
我们对于 Scitter 有几个目标
● 简化 Twitter 访问 比过去打开 HTTP 连接然后 手动 执行操作更容易
● 可以从 Java 客户机轻松访问它
● 轻松模拟以便进行测试
在这一期 我们不必完成整个 Twitter API 但是我们将完成一些核心部分 目的是让这个库达到公共源代码控制库的程度 便于其他人来完成这项工作
到目前为止 Scitter
首先我们简单回顾一下到目前为止我们所处的阶段
清单 Scitter v
package tedneward scitter import mons _ auth _ methods _ params _ import scala xml _ /** * Status message type This will typically be the most mon message type * sent back from Twitter (usually in some kind of collection form) Note * that all optional elements in the Status type are represented by the * Scala Option[T] type since that s what it s there for */ abstract class Status /** * Nested User type This could be bined with the top level User type * if we decide later that it s OK for this to have a boatload of optional * elements including the most recently posted status update (which is a * tad circular) */ abstract class User val id : Long val name : String val screenName : String val description : String val location : String val profileImageUrl : String val url : String val protectedUpdates : Boolean val followersCount : Int /** * Object wrapper for transforming (format) into User instances */ object User /* def fromAtom(node : Node) : Status = */ /* def fromRss(node : Node) : Status = */ def fromXml(node : Node) : User = new User val id = (node \\ id ) text toLong val name = (node \\ name ) text val screenName = (node \\ screen_name ) text val description = (node \\ description ) text val location = (node \\ location ) text val profileImageUrl = (node \\ profile_image_url ) text val url = (node \\ url ) text val protectedUpdates = (node \\ protected ) text toBoolean val followersCount = (node \\ followers_count ) text toInt val createdAt : String val id : Long val text : String val source : String val truncated : Boolean val inReplyToStatusId : Option[Long] val inReplyToUserId : Option[Long] val favorited : Boolean val user : User /** * Object wrapper for transforming (format) into Status instances */ object Status /* def fromAtom(node : Node) : Status = */ /* def fromRss(node : Node) : Status = */ def fromXml(node : Node) : Status = new Status val createdAt = (node \\ created_at ) text val id = (node \\ id ) text toLong val text = (node \\ text ) text val source = (node \\ source ) text val truncated = (node \\ truncated ) text toBoolean val inReplyToStatusId = if ((node \\ in_reply_to_status_id ) text != ) Some((node \\ in_reply_to_status_id ) text toLong) else None val inReplyToUserId = if ((node \\ in_reply_to_user_id ) text != ) Some((node \\ in_reply_to_user_id ) text toLong) else None val favorited = (node \\ favorited ) text toBoolean val user = User fromXml((node \\ user )( )) /** * Object for consuming non specific Twitter feeds such as the public timeline * Use this to do non authenticated requests of Twitter feeds */ object Scitter /** * Ping the server to see if it s up and running * * Twitter docs say: * test * Returns the string ok in the requested format with a OK HTTP status code * URL: * Formats: xml json * Method(s): GET */ def test : Boolean = val client = new HttpClient() val method = new GetMethod( ) method getParams() setParameter(HttpMethodParams RETRY_HANDLER new DefaultHttpMethodRetryHandler( false)) client executeMethod(method) val statusLine = method getStatusLine() statusLine getStatusCode() == /** * Query the public timeline for the most recent statuses * * Twitter docs say: * public_timeline * Returns the 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 seconds so requesting it more often than * that is a waste of resources * URL: _timeline format * Formats: xml json rss atom * Method(s): GET * API limit: Not applicable * Returns: list of status elements */ def publicTimeline : List[Status] = import llection mutable ListBuffer val client = new HttpClient() val method = new GetMethod( _timeline xml ) method getParams() setParameter(HttpMethodParams RETRY_HANDLER new DefaultHttpMethodRetryHandler( false)) client executeMethod(method) val statusLine = method getStatusLine() if (statusLine getStatusCode() == ) val responseXML = XML loadString(method getResponseBodyAsString()) val statusListBuffer = new ListBuffer[Status] for (n < (responseXML \\\\ status ) elements) statusListBuffer += (Status fromXml(n)) statusListBuffer toList else Nil /** * Class for consuming authenticated user Twitter APIs Each instance is * thus tied to a particular authenticated user on Twitter and will * behave accordingly (according to the Twitter API documentation) */ class Scitter(username : String password : String) /** * Verify the user credentials against Twitter * * Twitter docs say: * verify_credentials * Returns an HTTP OK response code and a representation of the * requesting user if authentication was successful; returns a status * code and an error message if not Use this method to test if supplied * user credentials are valid * URL: _credentials format * Formats: xml json * Method(s): GET */ def verifyCredentials : Boolean = val client = new HttpClient() val method = new GetMethod( ) method getParams() setParameter(HttpMethodParams RETRY_HANDLER new DefaultHttpMethodRetryHandler( false)) client getParams() setAuthenticationPreemptive(true) val creds = new UsernamePasswordCredentials(username password) client getState() setCredentials( new AuthScope( AuthScope ANY_REALM) creds) client executeMethod(method) val statusLine = method getStatusLine() statusLine getStatusCode() ==
代码有点长 但是很容易分为几个基本部分
● case 类 User 和 Status 表示 Twitter 在对 API 调用的响应中发回给客户机的基本类型 包括用于构造或提取 XML 的一些方法
● 一个 Scitter 独立对象 处理那些不需要对用户进行验证的操作
● 一个 Scitter 实例(用 username 和 password 参数化) 用于那些需要对用户执行验证的操作
到目前为止 对于这两种 Scitter 类型 我们只谈到了测试 verifyCredentials 和 public_timeline API 虽然这些有助于确定 HTTP 访问的基础(使用 Apache HttpClient 库)可以工作 并且我们将 XML 响应转换成 Status 对象的基本方式也是可行的 但是现在我们甚至不能进行基本的 我的朋友在说什么 的公共时间线查询 也没有采取过基本的措施来防止代码库中出现 重复 问题 更不用说寻找一些方法来模拟用于测试的网络访问代码
显然 在这一期我们有很多事情要做
连接
对于代码 第一件让我烦恼的事就是 我在 Scitter 对象和类的每个方法中都重复这样的操作序列 创建 HttpClient 实例 对它进行初始化 用必要的验证参数对它进行参数化 等等 当它们只有 个方法时 可以进行管理 但是显然不易于伸缩 而且以后还会增加很多方法 此外 以后重新在那些方法中引入模拟和/或本地/离线测试功能将十分困难 所以我们要解决这个问题
实际上 我们这里介绍的并不是 Scala 本身 而是不要重复自己(Don t Repeat Yourself)的思想 因此 我将从基本的面向对象方法开始 创建一个 helper 方法 用于做实际工作
清单 对代码库执行 DRY 原则
package tedneward scitter // object Scitter // private[scitter] def exec ute(url : String) = val client = new HttpClient() val method = new GetMethod(url) method getParams() setParameter(HttpMethodParams RETRY_HANDLER new DefaultHttpMethodRetryHandler( false)) client executeMethod(method) (method getStatusLine() getStatusCode() method getResponseBodyAsString())
注意两点 首先 我从 execute() 方法返回一个元组 其中包含状态码和响应主体 这正是让元组成为语言中固有的一部分的一个强大之处 因为实际上很容易从一个方法调用返回多个返回值 当然 在 Java 代码中 也可以通过创建包含元组元素的顶级或嵌套类来实现这一点 但是这需要一整套专用于这一个方法的代码 此外 本来也可以返回一个包含 String 键和 Object 值的 Map 但是那样就在很大程度上丧失了类型安全性 元组并不是一个非常具有变革性的特性 它只不过是又一个使 Scala 成为强大语言的优秀特性
由于使用元组 我需要使用 Scala 的另一个特色语法将两个结果都捕捉到本地变量中 就像下面这个重写后的 Scitter test 那样
清单 这符合 DRY 原则吗?
package tedneward scitter // object Scitter /** * Ping the server to see if it s up and running * * Twitter docs say: * test * Returns the string ok in the requested format with a OK HTTP status code * URL: * Formats: xml json * Method(s): GET */ def test : Boolean = val (statusCode statusBody) = execute( _timeline xml ) statusCode ==
实际上 我可以轻松地将 statusBody 全部去掉 并用 _ 替代它 因为我没有用过第二个参数(test 没有返回 statusBody) 但是对于其他调用将需要这个 statusBody 所以出于演示的目的 我保留了该参数
注意 execute() 没有泄露任何与实际 HTTP 通信相关的细节 — 这是 Encapsulation 这样便于以后用其他实现替换 execute()(以后的确要这么做) 或者便于通过重用 HttpClient 对象来优化代码 而不是每次重新实例化新的对象
接下来 注意到 execute() 方法在 Scitter 对象上吗?这意味着我将可以从不同的 Scitter 实例中使用它(至少现在可以这样做 如果以后在 execute() 内部执行的操作不允许这样做 则另当别论)— 这就是我将 execute() 标记为 private[scitter] 的原因 这意味着 tedneward scitter 包中的所有内容都可以看到它
(顺便说一句 如果还没有运行测试的话 那么请运行测试 确保一切运行良好 我将假设我们在讨论代码时您会运行测试 所以如果我忘了提醒您 并不意味着您也忘记这么做 )
顺便说一句 对于经过验证的访问 为了支持 Scitter 类 需要一个用户名和密码 所以我将创建一个重载的 execute() 方法 该方法新增两个 String 参数
清单 更加 DRY 化的版本
package tedneward scitter // object Scitter // private[scitter] def execute(url : String username : String password : String) = val client = new HttpClient() val method = new GetMethod(url) method getParams() setParameter(HttpMethodParams RETRY_HANDLER new DefaultHttpMethodRetryHandler( false)) client getParams() setAuthenticationPreemptive(true) client getState() setCredentials( new AuthScope( AuthScope ANY_REALM) new UsernamePasswordCredentials(username password)) client executeMethod(method) (method getStatusLine() getStatusCode() method getResponseBodyAsString())
实际上 除了验证部分 这两个 execute() 基本上是做相同的事情 我们可以按照第二个版本完全重写第一个 execute() 但是要注意 Scala 要求显式表明重载的 execute() 的返回类型
清单 放弃 DRY
package tedneward scitter // object Scitter // private[scitter] def execute(url : String) : (Int String) = execute(url ) private[scitter] def execute(url : String username : String password : String) = val client = new HttpClient() val method = new GetMethod(url) method getParams() setParameter(HttpMethodParams RETRY_HANDLER new DefaultHttpMethodRetryHandler( false)) if ((username != ) && (password != )) client getParams() setAuthenticationPreemptive(true) client getState() setCredentials( new AuthScope( AuthScope ANY_REALM) new UsernamePasswordCredentials(username password)) client executeMethod(method) (method getStatusLine() getStatusCode() method getResponseBodyAsString())
到目前为止 一切良好 我们对 Scitter 的通信部分进行了 DRY 化处理 接下来我们转移到下一件事情 获得朋友的 eet 的列表
连接(到朋友)
Twitter API 表明 friends_timeline API 调用 返回认证用户和该用户的朋友发表的最近 条状态 (它还指出 对于直接从 Twitter Web 站点使用 Twitter 的用户 这相当于 Web 上的 /home )对于任何 Twitter API 来说 这是非常基本的要求 所以让我们将它添加到 Scitter 类中 之所以将它添加到类而不是对象中 是因为正如文档中指出的那样 这是以验证用户的身份做的事情 而我已经决定归入 Scitter 类 而不是 Scitter 对象
但是 这里我们碰到一块绊脚石 friends_timeline 调用接受一组 可选参数 包括 since_id max_id count 和 page 以控制返回的结果 这是一项比较复杂的操作 因为 Scala 不像其他语言(例如 Groovy JRuby 或 JavaScript)那样原生地支持 可选参数 的思想 但是我们首先来处理简单的东西 — 我们来创建一个 friendsTimeline 方法 该方法只执行一般的 非参数化的调用
清单 告诉我你身边的朋友是怎样的……
package tedneward scitter class Scitter def friendsTimeline : List[Status] = val (statusCode statusBody) = Scitter execute( _timeline xml username password) if (statusCode == ) val responseXML = XML loadString(statusBody) val statusListBuffer = new ListBuffer[Status] for (n < (responseXML \\\\ status ) elements) statusListBuffer += (Status fromXml(n)) statusListBuffer toList else Nil
到目前为止 一切良好 用于测试的相应方法看上去如下所示
清单 我能判断您是怎样的人 (Miguel de Cervantes)
package tedneward scitter test class ScitterTests // @Test def scitterFriendsTimeline = val scitter = new Scitter(testUser testPassword) val result = scitter friendsTimeline assertTrue(result length > )
好极了 看上去就像 Scitter 对象中的 publicTimeline() 方法 并且行为也几乎完全相同
对于我们来说 那些可选参数仍然有问题 因为 Scala 并没有可选参数这样的语言特性 乍一看来 惟一的选择就是完整地创建重载的 friendsTimeline() 方法 让该方法带有一定数量的参数
幸运的是 还有一种更好的方式 即通过一种有趣的方式将 Scala 的两个语言特性(有一个特性我还没有提到过) — case 类和 重复参数 结合起来(见清单 )
清单 我有多爱你?……
package tedneward scitter // abstract class OptionalParam case class Id(id : String) extends OptionalParam case class UserId(id : Long) extends OptionalParam case class Since(since_id : Long) extends OptionalParam case class Max(max_id : Long) extends OptionalParam case class Count(count : Int) extends OptionalParam case class Page(page : Int) extends OptionalParam class Scitter(username : String password : String) // def friendsTimeline(options : OptionalParam*) : List[Status] = val optionsStr = new StringBuffer( _timeline xml? ) for (option < options) option match case Since(since_id) => optionsStr append( since_id= + since_id toString() + & ) case Max(max_id) => optionsStr append( max_id= + max_id toString() + & ) case Count(count) => optionsStr append( count= + count toString() + & ) case Page(page) => optionsStr append( page= + page toString() + & ) val (statusCode statusBody) = Scitter execute(optionsStr toString() username password) if (statusCode == ) val responseXML = XML loadString(statusBody) val statusListBuffer = new ListBuffer[Status] for (n < (responseXML \\\\ status ) elements) statusListBuffer += (Status fromXml(n)) statusListBuffer toList else Nil
看到标在选项参数后面的 * 吗?这表明该参数实际上是一个参数序列 这类似于 Java 中的 varargs 结构 和 varargs 一样 传递的参数数量可以像前面那样为 (不过 我们将需要回到测试代码 向 friendsTimeline 调用增加一对括号 否则编译器无法作出判断 是调用不带参数的方法 还是出于部分应用之类的目的而调用该方法) 我们还可以开始传递那些 case 类型 如下面的清单所示
清单 ……听我细细说 (William Shakespeare)
package tedneward scitter test class ScitterTests // @Test def scitterFriendsTimelineWithCount = val scitter = new Scitter(testUser testPassword) val result = scitter friendsTimeline(Count( )) assertTrue(result length == )
当然 总是存在这样的可能性 客户机传入古怪的参数序列 例如 friendsTimeline(Count( ) Count( ) Count( )) 但是在这里 我们只是将列表传递给 Twitter(希望它们的错误处理足够强大 能够只采用指定的最后那个参数) 当然 如果真的担心这一点 也很容易在构造发送到 Twitter 的 URL 之前 从头至尾检查重复参数列表 并采用指定的每种参数的最后一个参数 不过 后果自负
兼容性
但是 这又产生一个有趣的问题 从 Java 代码调用这个方法有多容易?毕竟 如果这个库的主要目标之一是维护与 Java 代码的兼容性 那么我们需要确保 Java 代码在使用它时不至于太麻烦
我们首先通过我们的好朋友 javap 检验一下 Scitter 类
清单 哦 没错 Java 代码……我现在想起来了……
C:\\>javap classpath classes tedneward scitter ScitterCompiled from scitter scala public class tedneward scitter Scitter extends java lang Object implements scala ScalaObject public tedneward scitter Scitter(java lang String java lang String); public scala List friendsTimeline(scala Seq); public boolean verifyCredentials(); public int $tag() throws java rmi RemoteException;
这时我心中有两点担心 首先 friendsTimeline() 带有一个 scala Seq 参数(这是我们刚才用过的重复参数特性) 其次 friendsTimeline() 方法和 Scitter 对象中的 publicTimeline() 方法一样(如果不信 可以运行 javap 查证) 返回一个元素列表 scala List 这两种类型在 Java 代码中有多好用?
最简单的方法是用 Java 代码而不是 Scala 编写一组小型的 JUnit 测试 所以接下来我们就这样做 虽然可以测试 Scitter 实例的构造 并调用它的 verifyCredentials() 方法 但这些并不是特别有用 — 记住 我们不是要验证 Scitter 类的正确性 而是要看看从 Java 代码中使用它有多容易 为此 我们直接编写一个测试 该测试将获取 friends timeline — 换句话说 我们要实例化一个 Scitter 实例 并且不使用任何参数来调用它的 friendsTimeline() 方法
这有点复杂 因为需要传入 scala Seq 参数 — scala Seq 是一个 Scala 特性 它将映射到底层 JVM 中的一个接口 所以不能直接实例化 我们可以尝试典型的 Java null 参数 但是这样做会在运行时抛出异常 我们需要的是一个 scala Seq 类 以便从 Java 代码中轻松地实例化这个类
最终 我们还是在 mutable ListBuffer 类型中找到一个这样的类 这正是在 Scitter 实现本身中使用的类型
清单 现在我明白了自己为什么喜欢 Scala……
package tedneward scitter test;import junit *;import tedneward scitter *;public class JavaScitterTests public static final String testUser = TESTUSER ; public static final String testPassword = TESTPASSWORD ; @Test public void getFriendsStatuses() Scitter scitter = new Scitter(testUser testPassword); if (scitter verifyCredentials()) scala List statuses = scitter friendsTimeline(new llection mutable ListBuffer()); Assert assertTrue(statuses length() > ); else Assert assertTrue(false);
使用返回的 scala List 不是问题 因为我们可以像对待其他 Collection 类一样对待它(不过我们的确怀念 Collection 的一些优点 因为 List 上基于 Scala 的方法都假设您将从 Scala 中与它们交互) 所以 遍历结果并不难 只要用上一点 旧式 Java 代码(大约 年时候的风格)
清单 重回 又见 Vector……
package tedneward scitter test;import junit *;import tedneward scitter *;public class JavaScitterTests public static final String testUser = TESTUSER ; public static final String testPassword = TESTPASSWORD ; @Test public void getFriendsStatuses() Scitter scitter = new Scitter(testUser testPassword); if (scitter verifyCredentials()) scala List statuses = scitter friendsTimeline(new llection mutable ListBuffer()); Assert assertTrue(statuses length() > ); for (int i= ; i<statuses length(); i++) Status stat = (Status)statuses apply(i); System out println(stat user() screenName() + said + stat text()); else Assert assertTrue(false);
这将我们引向另一个部分 即将参数传递到 friendsTimeline() 方法 不幸的是 ListBuffer 类型不是将一个集合作为构造函数参数 所以我们必须构造参数列表 然后将集合传递到方法调用 这样有些单调乏味 但还可以承受
清单 现在可以回到 Scala 吗?
package tedneward scitter test;import junit *;import tedneward scitter *;public class JavaScitterTests public static final String testUser = TESTUSER ; public static final String testPassword = TESTPASSWORD ; // @Test public void getFriendsStatusesWithCount() Scitter scitter = new Scitter(testUser testPassword); if (scitter verifyCredentials()) llection mutable ListBuffer params = new llection mutable ListBuffer(); params $plus$eq(new Count( )); scala List statuses = scitter friendsTimeline(params); Assert assertTrue(statuses length() > ); Assert assertTrue(statuses length() == ); for (int i= ; i<statuses length(); i++) Status stat = (Status)statuses apply(i); System out println(stat user() screenName() + said + stat text()); else Assert assertTrue(false);
所以 虽然 Java 版本比对应的 Scala 版本要冗长一点 但是到目前为止 从任何要使用 Scitter 库的 Java 客户机中调用该库仍然非常简单 好极了
结束语
显然 对于 Scitter 还有很多事情要做 但是它已经逐渐丰满起来 感觉不错 我们设法对 Scitter 库的通信部分进行了 DRY 化处理 并且为 Twitter 提供的很多不同的 API 调用合并了所需的可选参数 — 到目前为止 Java 客户机基本上没有受到我们公布的 API 的拖累 即使 API 没有 Scala 所使用的那些 API 那样干净 但是如果 Java 开发人员要使用 Scitter 库 也不需要费太多力气
Scitter 库仍然带有对象的意味 不过我们也开始看到 一些有实用意义的 Scala 特性正在出现 随着我们继续构建这个库 只要有助于使代码更简洁 更清晰 越来越多这样的特性将添加进来 本应如此
cha138/Article/program/Java/JSP/201311/19590相关参考
知识大全 面向Java开发人员的Scala指南: Scala控制结构内部揭密
面向Java开发人员的Scala指南:Scala控制结构内部揭密 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起
知识大全 面向Java开发人员的Scala指南: 面向对象的函数编程
面向Java开发人员的Scala指南:面向对象的函数编程 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下
面向Java开发人员的Scala指南:实现继承 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! S
知识大全 面向Java开发人员的Scala指南: 深入了解Scala并发性 了解 Scala 如何简化并发编
面向Java开发人员的Scala指南:深入了解Scala并发性了解Scala如何简化并发编 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后
知识大全 面向Java开发人员的Scala指南: 关于特征和行为
面向Java开发人员的Scala指南:关于特征和行为 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!
知识大全 面向Java开发人员的Scala指南: 包和访问修饰符
面向Java开发人员的Scala指南:包和访问修饰符 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!
知识大全 面向Java开发人员的Scala指南: 构建计算器,第1 部分
面向Java开发人员的Scala指南:构建计算器,第1部分 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一
知识大全 面向Java开发人员的Scala指南: 构建计算器,第 2 部分
面向Java开发人员的Scala指南:构建计算器,第2部分 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一
知识大全 面向Java开发人员的Scala指南: 深入了解Scala并发性 了解 actor 如何提供新的应
面向Java开发人员的Scala指南:深入了解Scala并发性了解actor如何提供新的应 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后
知识大全 Java开发者的Scala指南: Scala+Twitter=Scitter
Java开发者的Scala指南:Scala+Twitter=Scitter 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我