半岛外围网上直营

Java开发中Scala的使用指南(一)

原创|其它|编辑:郝浩|2009-09-03 11:11:13.000|阅读 580 次

概述:抽象地谈论 Scala 的确有趣,然而一旦将其付诸实践,就会发现将它作为 “玩具” 与在工作中使用它的区别。Scala 狂热者 Ted Neward 撰写了一篇 对 Scitter 的介绍,Scitter 是一个用于访问 Twitter 的 Scala 库,本文是其后续篇,在本文中,Ted Neward 为这个客户机库提供了一组更有趣也更有用的特性。

#慧都22周年庆大促·界面/图表报表/文档/IDE/IOT/测试等千款热门软控件火热促销中>>

  抽象地谈论 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 0.1

  首先我们简单回顾一下到目前为止我们所处的阶段:

  清单 1. Scitter v0.1

package com.tedneward.scitter
  {
  import org.apache.commons.httpclient._, auth._, methods._, params._
  import scala.xml._
  /**
  * Status message type. This will typically be the most common 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 combined 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")(0))
  }
  }
  }
  /**
  * 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 200 OK HTTP status code.
  * URL: //twitter.com/help/test.format
  * Formats: xml, json
  * Method(s): GET
  */
  def test : Boolean =
  {
  val client = new HttpClient()
  val method = new GetMethod("//twitter.com/help/test.xml")
  method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
  new DefaultHttpMethodRetryHandler(3, false))
  client.executeMethod(method)
  val statusLine = method.getStatusLine()
  statusLine.getStatusCode() == 200
  }
  /**
  * 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: //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("//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
  }
  }
  }
  /**
  * 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 200 OK response code and a representation of the
  * requesting user if authentication was successful; returns a 401 status
  * code and an error message if not. Use this method to test if supplied
  * user credentials are valid.
  * URL: //twitter.com/account/verify_credentials.format
  * Formats: xml, json
  * Method(s): GET
  */
  def verifyCredentials : Boolean =
  {
  val client = new HttpClient()
  val method = new GetMethod("//twitter.com/help/test.xml")
  method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
  new DefaultHttpMethodRetryHandler(3, false))
  client.getParams().setAuthenticationPreemptive(true)
  val creds = new UsernamePasswordCredentials(username, password)
  client.getState().setCredentials(
  new AuthScope("twitter.com", 80, AuthScope.ANY_REALM), creds)
  client.executeMethod(method)
  val statusLine = method.getStatusLine()
  statusLine.getStatusCode() == 200
  }
  }
  }

  代码有点长,但是很容易分为几个基本部分:

  ● case 类 User 和 Status,表示 Twitter 在对 API 调用的响应中发回给客户机的基本类型,包括用于构造或提取 XML 的一些方法。

  ● 一个 Scitter 独立对象,处理那些不需要对用户进行验证的操作。

  ● 一个 Scitter 实例(用 username 和 password 参数化),用于那些需要对用户执行验证的操作。

  到目前为止,对于这两种 Scitter 类型,我们只谈到了测试、verifyCredentials 和 public_timeline API.虽然这些有助于确定 HTTP 访问的基础(使用 Apache HttpClient 库)可以工作,并且我们将 XML 响应转换成 Status 对象的基本方式也是可行的,但是现在我们甚至不能进行基本的 “我的朋友在说什么” 的公共时间线查询,也没有采取过基本的措施来防止代码库中出现 “重复” 问题,更不用说寻找一些方法来模拟用于测试的网络访问代码。

  显然,在这一期我们有很多事情要做。

  连接

  对于代码,第一件让我烦恼的事就是,我在 Scitter 对象和类的每个方法中都重复这样的操作序列:创建 HttpClient 实例,对它进行初始化,用必要的验证参数对它进行参数化,等等。当它们只有 3 个方法时,可以进行管理,但是显然不易于伸缩,而且以后还会增加很多方法。此外,以后重新在那些方法中引入模拟和/或本地/离线测试功能将十分困难。所以我们要解决这个问题。

  实际上,我们这里介绍的并不是 Scala 本身,而是不要重复自己(Don't-Repeat-Yourself)的思想。因此,我将从基本的面向对象方法开始:创建一个 helper 方法,用于做实际工作:

  清单 2. 对代码库执行 DRY 原则

 package com.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(3, false))
  client.executeMethod(method)
  (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
  }
  }
  }

  注意两点:首先,我从 execute() 方法返回一个元组,其中包含状态码和响应主体。这正是让元组成为语言中固有的一部分的一个强大之处,因为实际上很容易从一个方法调用返回多个返回值。当然,在 Java 代码中,也可以通过创建包含元组元素的顶级或嵌套类来实现这一点,但是这需要一整套专用于这一个方法的代码。此外,本来也可以返回一个包含 String 键和 Object 值的 Map,但是那样就在很大程度上丧失了类型安全性。元组并不是一个非常具有变革性的特性,它只不过是又一个使 Scala 成为强大语言的优秀特性。

  由于使用元组,我需要使用 Scala 的另一个特色语法将两个结果都捕捉到本地变量中,就像下面这个重写后的 Scitter.test 那样:

  清单 3. 这符合 DRY 原则吗?

 package com.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 200 OK HTTP status code.
  * URL: //twitter.com/help/test.format
  * Formats: xml, json
  * Method(s): GET
  */
  def test : Boolean =
  {
  val (statusCode, statusBody) =
  execute("//twitter.com/statuses/public_timeline.xml")
  statusCode == 200
  }
  }
  }

  实际上,我可以轻松地将 statusBody 全部去掉,并用 _ 替代它,因为我没有用过第二个参数(test 没有返回 statusBody),但是对于其他调用将需要这个 statusBody,所以出于演示的目的,我保留了该参数。

  注意,execute() 没有泄露任何与实际 HTTP 通信相关的细节 — 这是 Encapsulation 101.这样便于以后用其他实现替换 execute()(以后的确要这么做),或者便于通过重用 HttpClient 对象来优化代码,而不是每次重新实例化新的对象。

  接下来,注意到 execute() 方法在 Scitter 对象上吗?这意味着我将可以从不同的 Scitter 实例中使用它(至少现在可以这样做,如果以后在 execute() 内部执行的操作不允许这样做,则另当别论)— 这就是我将 execute() 标记为 private[scitter] 的原因,这意味着 com.tedneward.scitter 包中的所有内容都可以看到它。

  (顺便说一句,如果还没有运行测试的话,那么请运行测试,确保一切运行良好。我将假设我们在讨论代码时您会运行测试,所以如果我忘了提醒您,并不意味着您也忘记这么做。)

  顺便说一句,对于经过验证的访问,为了支持 Scitter 类,需要一个用户名和密码,所以我将创建一个重载的 execute() 方法,该方法新增两个 String 参数:

  清单 4. 更加 DRY 化的版本

 package com.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(3, false))
  client.getParams().setAuthenticationPreemptive(true)
  client.getState().setCredentials(
  new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
  new UsernamePasswordCredentials(username, password))
  client.executeMethod(method)
  (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
  }
  }
  }

  实际上,除了验证部分,这两个 execute() 基本上是做相同的事情,我们可以按照第二个版本完全重写第一个 execute(),但是要注意,Scala 要求显式表明重载的 execute() 的返回类型:

  清单 5. 放弃 DRY

 package com.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(3, false))
  if ((username != "") && (password != ""))
  {
  client.getParams().setAuthenticationPreemptive(true)
  client.getState().setCredentials(
  new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
  new UsernamePasswordCredentials(username, password))
  }
  client.executeMethod(method)
  (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
  }
  }
  }

  到目前为止,一切良好。我们对 Scitter 的通信部分进行了 DRY 化处理,接下来我们转移到下一件事情:获得朋友的 tweet 的列表。


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@ke049m.cn

文章转载自:IT专家网

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP
利记足球官网(官方)网站/网页版登录入口/手机版登录入口-最新版(已更新) 真人boyu·博鱼滚球网(官方)网站/网页版登录入口/手机版登录入口-最新版(已更新) 最大网上PM娱乐城盘口(官方)网站/网页版登录入口/手机版登录入口-最新版(已更新) 正规雷火竞技官方买球(官方)网站/网页版登录入口/手机版登录入口-最新版(已更新) 雷火竞技权威十大网(官方)网站/网页版登录入口/手机版登录入口-最新版(已更新) boyu·博鱼信誉足球官网(官方)网站/网页版登录入口/手机版登录入口-最新版(已更新) 权威188BET足球网(官方)网站/网页版登录入口/手机版登录入口-最新版(已更新) 正规188BET足球大全(官方)网站/网页版登录入口/手机版登录入口-最新版(已更新)