原文标题:Converting Plaid to Kotlin: Lessons learned (Part 2)

原文链接:http://antonioleiva.com/plaid-kotlin-2/

原文作者:Antonio Leiva(http://antonioleiva.com/about/

原文发布:2015-11-17

我们在第一部分中所见的各种显著地改进,要归功于在Activity中使用了Kotlin语言。但是,由于主要是重载方法做些事情,仍然免不了一些公式化代码,所以这种类型的类还不能很好的展示其效果。

我持续用Kotlin语言改写该APP(大家可以在repo看到所有改变),并遇到一些有趣的事情。今天,我着重谈论DataManager类的转换。透露一下,该类的大小已从422行减少到177行。我认为这很容易理解。

When

纵观该类时,首先看到最多的就是在loadSource类中有大量的if/else语句。在第一个实例中,可以用switch语句提升可读性,但是总还是有点不易理解。在Kotlin语言中,可以用when expression(表达式),它非常类似Java语言中的switch语句,但是功能更强。条件可以依据需要编写,且可以很好的替代if/else语句。这里使用其最简单版本,也已经可以使这个方法更容易阅读:

 when (source.key) {
SourceManager.SOURCE_DESIGNER_NEWS_POPULAR -> loadDesignerNewsTopStories(page)
SourceManager.SOURCE_DESIGNER_NEWS_RECENT -> loadDesignerNewsRecent(page)
SourceManager.SOURCE_DRIBBBLE_POPULAR -> loadDribbblePopular(page)
SourceManager.SOURCE_DRIBBBLE_FOLLOWING -> loadDribbbleFollowing(page)
SourceManager.SOURCE_DRIBBBLE_USER_LIKES -> loadDribbbleUserLikes(page)
SourceManager.SOURCE_DRIBBBLE_USER_SHOTS -> loadDribbbleUserShots(page)
SourceManager.SOURCE_DRIBBBLE_RECENT -> loadDribbbleRecent(page)
SourceManager.SOURCE_DRIBBBLE_DEBUTS -> loadDribbbleDebuts(page)
SourceManager.SOURCE_DRIBBBLE_ANIMATED -> loadDribbbleAnimated(page)
SourceManager.SOURCE_PRODUCT_HUNT -> loadProductHunt(page)
else -> when (source) {
is Source.DribbbleSearchSource -> loadDribbbleSearch(source, page)
is Source.DesignerNewsSearchSource -> loadDesignerNewsSearch(source, page) }
}

然而,这不是case,是when表达式,所以可以返回一个值,这非常有用。

映射处理

虽然,这个例子并没有减少多少代码(行),但是可以有趣的看到,在Kotlin中怎样处理映射表。在Java语言中,getNextPageIndex如下:

 private int getNextPageIndex(String dataSource) {
int nextPage = 1; // default to one – i.e. for newly added sources
if (pageIndexes.containsKey(dataSource)) {
nextPage = pageIndexes.get(dataSource) + 1;
}
pageIndexes.put(dataSource, nextPage);
return nextPage;
}

那在Kotlin语言中是怎样的?

 private fun getNextPageIndex(dataSource: String): Int {
val nextPage = 1 + pageIndexes.getOrElse(dataSource) { 0 }
pageIndexes += dataSource to nextPage
return nextPage
}

这里有些有趣的事,首先是getOrElse函数,如果在映射表中,没有找到元素,则允许返回默认值:

 val nextPage = 1 + pageIndexes.getOrElse(dataSource) { 0 }

多亏这一点,我们可以节省一些条件检查代码。而另一件事更有趣的事是,如何在映射表中添加新的项。Kotlin语言的映射表实现了“+”操作符,这样添加新项就可以如此:map = map + Pair,还可以 map += Pair。即:

 pageIndexes += Pair(dataSource, nextPage)

但是,我之前可能说过,可以用to(中缀函数)返回Pair。这样前一行就如同:

 pageIndexes += dataSource to nextPage

将callback(回调函数)转换到Lambda表达式

这如同变魔术一样。在装载类中有许多重复的代码。它们大多数有复制来的相同结构,而其它则是调用API。首先,创建Retrofit回调函数。如果请求成功,无论需要什么都可以从结果获取必要的信息。最后,调用“数据装载”侦听器。在任何情况(无论成功或失败)下,loadingCount都更新了。

仅举一例:

 private void loadDesignerNewsTopStories(final int page) {
getDesignerNewsApi().getTopStories(page, new Callback<StoriesResponse>() {
@Override
public void success(StoriesResponse storiesResponse, Response response) {
if (storiesResponse != null
&& sourceIsEnabled(SourceManager.SOURCE_DESIGNER_NEWS_POPULAR)) {
setPage(storiesResponse.stories, page);
setDataSource(storiesResponse.stories,
SourceManager.SOURCE_DESIGNER_NEWS_POPULAR);
onDataLoaded(storiesResponse.stories);
}
loadingCount.decrementAndGet();
} @Override
public void failure(RetrofitError error) {
loadingCount.decrementAndGet();
}
});
}

如果不分析,这段代码理解起来相当困难。我们可以创建一个回调函数,它创建retrofit回调函数,实现前面回调函数的结构。所不同的是从结果中获取必要的信息:

 private inline fun <T> callback(page: Int, sourceKey: String,
crossinline extract: (T) -> List<PlaidItem>)
= retrofitCallback<T> { result, response ->
if (sourceIsEnabled(sourceKey)) {
val items = extract(result)
setPage(items, page)
setDataSource(items, sourceKey)
onDataLoaded(items)
}
}

这十分相似:检查是否启用源,如果启用,就调用setPagesetDataSourceonDataLoaded从结果中获取相关项。retrofitCallback的实现可使前面的函数更简单,而它可以省略了:

 private inline fun <T> retrofitCallback(crossinline code: (T, Response) -> Unit): Callback<T>
= object : Callback<T> {
override fun success(t: T?, response: Response) {
t?.let { code(it, response) }
loadingCount.decrementAndGet()
} override fun failure(error: RetrofitError) {
loadingCount.decrementAndGet()
}
}

inline修饰符是优化函数的方法,该函数的参数包含其它函数。当函数包含lambda表达式时,其转换等同于对象包含了函数的实现代码。然而,如果用inline,lambda表达式将在被调用时由它的代码来替换。如果lambda表达式返回一个值,就要用crossinline。更多资料请阅读Kotlin参考资料

两个函数是泛型,所以可以接受任何要求的类型。这样,前面的请求就可以如下:

 private fun loadDesignerNewsTopStories(page: Int) {
designerNewsApi.getTopStories(page,
callback(page, SourceManager.SOURCE_DESIGNER_NEWS_POPULAR) { it.stories })
}

该函数调用getTopStories,创建接收page和源key的回调函数,函数从结果中获取stories。类似的结构为调用的其它部分运行,但是无论需要什么结果都可以做。例如,另一个响应需要修改包含的user:

 private fun loadDribbbleUserShots(page: Int) = dribbbleLogged {
val user = dribbblePrefs.user
dribbbleApi.getUserShots(page, DribbbleService.PER_PAGE_DEFAULT,
callback(page, SourceManager.SOURCE_DRIBBBLE_USER_SHOTS) {
// this api call doesn't populate the shot user field but we need it
it.apply { forEach { it.user = user } }
})
}

如你所见,前者还要求记录到dribbble中。dribbbleLogged函数负责检查,如果不是就做其它事。

 private inline fun dribbbleLogged(code: () -> Unit) {
if (dribbblePrefs.isLoggedIn) {
code()
} else {
loadingCount.decrementAndGet()
}
}

总结

这部分展示了lambda表达式能力和作为“第一类公民(first class citizen)”函数的使用。代码的减少是巨大的(减少238%),但还不是最重要的改进。现在代码更易阅读,错误更少。记得阅读我在Github的Plaid分支中最后的提交。

前一篇:http://www.cnblogs.com/figozhg/p/5041855.html

用Kotlin语言重新编写Plaid APP:经验教训(II)的更多相关文章

  1. 用Kotlin语言重新编写Plaid APP:经验教训(I)

    原文标题:Converting Plaid to Kotlin: Lessons learned (Part 1) 原文链接:http://antonioleiva.com/plaid-kotlin- ...

  2. 用React Native编写跨平台APP

    用React Native编写跨平台APP React Native 是一个编写iOS与Android平台实时.原生组件渲染的应用程序的框架.它基于React,Facebook的JavaScript的 ...

  3. 释放Android的函数式能量(I):Kotlin语言的Lambda表达式

    原文标题:Unleash functional power on Android (I): Kotlin lambdas 原文链接:http://antonioleiva.com/operator-o ...

  4. Kotlin 语言高级安卓开发入门

    过去一年,使用 Kotlin 来为安卓开发的人越来越多.即使那些现在还没有使用这个语言的开发者,也会对这个语言的精髓产生共鸣,它给现在 Java 开发增加了简单并且强大的范式.Jake Wharton ...

  5. 用Xamarin和Visual Studio编写iOS App

    一说开发 iOS app,你立马就会想到苹果的开发语言 Objective C/Swift 和 Xcode.但是,这并不是唯一的选择,我们完全可以使用别的语言和框架. 一种主流的替换方案是 Xamar ...

  6. 认识一下Kotlin语言,Android平台的Swift

    今天在CSDN首页偶然看到一个贴子JetBrains正式公布Kotlin 1.0:JVM和Android上更好用的语言 看完后,感觉Kotlin语法非常简洁,有一系列动态语言的特点,Lambda表达式 ...

  7. Hybrid App经验解读 一

    郑昀编纂 关键词:Hybrid,Zepto,Fastclick,Backbone,sui,SPA,pushState,跨域,CORS click 事件还是 tap 事件? Zepto 的 show/h ...

  8. Kotlin语言编程技巧集

    空语句 Kotlin 语言中的空语句有 {} Unit when (x) { 1 -> ... 2 -> ... else -> {} // else -> Unit } Wh ...

  9. Kotlin语言学习笔记(2)

    类(classes) // 类声明 class Invoice { } // 空的类 class Empty // 主体构造器(primary constructor) class Person co ...

随机推荐

  1. 豪情-CSS解构系列之-新浪页面解构-01

    目录: 一. 新浪的布局特点 二. 内容细节的特点 三. 其中相关的一些基础技术点 1. 常见布局方法 2. 布局要点 3. Debugger误区 4.列表 5.字体颜色 6.CSS选择符 7.CSS ...

  2. [PHP源码阅读]array_push和array_unshift函数

    在PHP中,在数组中添加元素也是一种很常用的操作,分别有在数组尾部和头部添加元素,看看PHP内部是如何实现数组插入的操作. 我在github有对PHP源码更详细的注解.感兴趣的可以围观一下,给个sta ...

  3. Atitit 常用二维码对比(QR、PDF417、DM、汉信码 Aztec code maxicode

    Atitit 常用二维码对比(QR.PDF417.DM.汉信码 Aztec code maxicode DM码则更"小",可在仅仅25mm²的面积上编码30个数字.但也就是因为太小 ...

  4. Livecoding.tv2.5发布,增加“用户搜索引擎”功能,方便用户找到匹配的程序员

    近日,在Livecoding.tv最新发布的博客中,介绍了该平台2.5版的一系列新功能,其中的User Discovery Engine(用户搜索引擎)受到大家的欢迎.使用该引擎,可以很方便地查找在L ...

  5. 高性能JavaScript--快速响应的用户界面(简要学习笔记三)

    1.浏览器线程:用于执行JavaScript和更新用户界面的进程被称为“浏览器UI线程”.   2. <1>定时器的出现让出UI线程控制权 setTimeout(),setInterval ...

  6. H5 Notes:Navigator Geolocation

    H5的地理位置API可以帮助我们来获取用户的地理位置,经纬度.海拔等,因此我们可以利用该API做天气应用.地图服务等. Geolocation对象是我们获取地理位置用到的对象. 首先判断浏览器是否支持 ...

  7. python基础操作以及hdfs操作

    目录 前言 基础操作 hdfs操作 总结 一.前言        作为一个全栈工程师,必须要熟练掌握各种语言...HelloWorld.最近就被"逼着"走向了python开发之路, ...

  8. LinqToDB 源码分析——生成与执行SQL语句

    生成SQL语句的功能可以算是LinqToDB框架的最后一步.从上一章中我们可以知道处理完表达式树之后,相关生成SQL信息会被保存在一个叫SelectQuery类的实例.有了这个实例我们就可以生成对应的 ...

  9. 如果你也会C#,那不妨了解下F#(1):F# 数据类型

    本文链接:http://www.cnblogs.com/hjklin/p/fs-for-cs-dev-1.html 简单介绍 F#(与C#一样,念作"F Sharp")是一种基于. ...

  10. Win10 UWP开发系列:实现Master/Detail布局

    在开发XX新闻的过程中,UI部分使用了Master/Detail(大纲/细节)布局样式.Win10系统中的邮件App就是这种样式,左侧一个列表,右侧是详情页面.关于这种 样式的说明可参看MSDN文档: ...