首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

面向 Java 开发人员的 Scala 指南 增强 Scitter 库-2

面向 Java 开发人员的 Scala 指南 增强 Scitter 库-2

关于 Twitter API如果您不熟悉 Twitter API,那么有必要花几分钟看看 ,了解更详细的信息。其基本原理很简单 — 在 URL 查询中传递参数,响应可采用 4 种格式之一(JSON、XML、ATOM 或 RSS),等等 — 但是,和所有 API 一样,细节往往非常重要,这里假设读者阅读本文时,已经在浏览器中打开了 Twitter API,以便将注意力集中到 Scala 上来。

到目前为止,对于这两种 Scitter 类型,我们只谈到了测试、verifyCredentials 和 public_timeline API。虽然这些有助于确定 HTTP 访问的基础(使用 Apache HttpClient 库)可以工作,并且我们将 XML 响应转换成 Status 对象的基本方式也是可行的,但是现在我们甚至不能进行基本的 “我的朋友在说什么” 的公共时间线查询,也没有采取过基本的措施来防止代码库中出现 “重复” 问题,更不用说寻找一些方法来模拟用于测试的网络访问代码。
显然,在这一期我们有很多事情要做。
连接对于代码,第一件让我烦恼的事就是,我在 Scitter 对象和类的每个方法中都重复这样的操作序列:创建 HttpClient 实例,对它进行初始化,用必要的验证参数对它进行参数化,等等。当它们只有 3 个方法时,可以进行管理,但是显然不易于伸缩,而且以后还会增加很多方法。此外,以后重新在那些方法中引入模拟和/或本地/离线测试功能将十分困难。所以我们要解决这个问题。
实际上,我们这里介绍的并不是 Scala 本身,而是不要重复自己(Don't-Repeat-Yourself)的思想。因此,我将从基本的面向对象方法开始:创建一个 helper 方法,用于做实际工作:
清单 2. 对代码库执行 DRY 原则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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())
    }
  }
}




牢记 DRY如果您还没有读过或者很久没有温习 Pragmatic Programmer,那么告诉您,DRY 表示“Don't Repeat Yourself”。这句话提醒您,每当您一遍又一遍地输入相同的代码时,就是在重复编码,最终您将发现需要将那些做同样事情的代码集中到一个地方(例如一个方法),以便于以后修复、增强或替换它。如果您到现在为止还没有读过 Pragmatic Programmer,那可够丢人的。您应该将阅读这本书作为这个月的家庭作业。

注意两点:首先,我从 execute() 方法返回一个元组,其中包含状态码和响应主体。这正是让元组成为语言中固有的一部分的一个强大之处,因为实际上很容易从一个方法调用返回多个返回值。当然,在 Java 代码中,也可以通过创建包含元组元素的顶级或嵌套类来实现这一点,但是这需要一整套专用于这一个方法的代码。此外,本来也可以返回一个包含 String 键和 Object 值的 Map,但是那样就在很大程度上丧失了类型安全性。元组并不是一个非常具有变革性的特性,它只不过是又一个使 Scala 成为强大语言的优秀特性。
由于使用元组,我需要使用 Scala 的另一个特色语法将两个结果都捕捉到本地变量中,就像下面这个重写后的 Scitter.test 那样:
清单 3. 这符合 DRY 原则吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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: http://twitter.com/help/test.format
     * Formats: xml, json
     * Method(s): GET
     */
    def test : Boolean =
    {
      val (statusCode, statusBody) =
        execute("http://twitter.com/statuses/public_timeline.xml")
      statusCode == 200
    }
  }
}




实际上,我可以轻松地将 statusBody 全部去掉,并用 _ 替代它,因为我没有用过第二个参数(test 没有返回 statusBody),但是对于其他调用将需要这个 statusBody,所以出于演示的目的,我保留了该参数。
注意,execute() 没有泄露任何与实际 HTTP 通信相关的细节 — 这是 Encapsulation 101。这样便于以后用其他实现替换 execute()(以后的确要这么做),或者便于通过重用 HttpClient 对象来优化代码,而不是每次重新实例化新的对象。
提前优化顺便说一句,只有当 HttpClient 在构造函数中做有实际意义的事情时,这种优化才有用。特别是,如果只是为了节省对象分配而缓存 HttpClient 对象,那么这不是 有用的优化 — 多年来在有关 Java 性能的文献中饱受非议的垃圾收集器,实际上非常善于分配和收集短时间存活的对象,例如这里的 HttpClient 对象和同类的对象。但是,最重要的是,只有当我发现 execute() 方法成为瓶颈或者造成资源浪费时,我才会应用这个优化。应针对具体情况采取相应的措施。

接下来,注意到 execute() 方法在 Scitter 对象上吗?这意味着我将可以从不同的 Scitter 实例中使用它(至少现在可以这样做,如果以后在 execute() 内部执行的操作不允许这样做,则另当别论)— 这就是我将 execute() 标记为 private[scitter] 的原因,这意味着 com.tedneward.scitter 包中的所有内容都可以看到它。
(顺便说一句,如果还没有运行测试的话,那么请运行测试,确保一切运行良好。我将假设我们在讨论代码时您会运行测试,所以如果我忘了提醒您,并不意味着您也忘记这么做。)
顺便说一句,对于经过验证的访问,为了支持 Scitter 类,需要一个用户名和密码,所以我将创建一个重载的 execute() 方法,该方法新增两个 String 参数:
清单 4. 更加 DRY 化的版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 的列表。
返回列表