• 用Kotlin语言重新编写Plaid APP:经验教训(II)


    原文标题: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语句。这里使用其最简单版本,也已经可以使这个方法更容易阅读:

     1 when (source.key) {
     2     SourceManager.SOURCE_DESIGNER_NEWS_POPULAR -> loadDesignerNewsTopStories(page)
     3     SourceManager.SOURCE_DESIGNER_NEWS_RECENT -> loadDesignerNewsRecent(page)
     4     SourceManager.SOURCE_DRIBBBLE_POPULAR -> loadDribbblePopular(page)
     5     SourceManager.SOURCE_DRIBBBLE_FOLLOWING -> loadDribbbleFollowing(page)
     6     SourceManager.SOURCE_DRIBBBLE_USER_LIKES -> loadDribbbleUserLikes(page)
     7     SourceManager.SOURCE_DRIBBBLE_USER_SHOTS -> loadDribbbleUserShots(page)
     8     SourceManager.SOURCE_DRIBBBLE_RECENT -> loadDribbbleRecent(page)
     9     SourceManager.SOURCE_DRIBBBLE_DEBUTS -> loadDribbbleDebuts(page)
    10     SourceManager.SOURCE_DRIBBBLE_ANIMATED -> loadDribbbleAnimated(page)
    11     SourceManager.SOURCE_PRODUCT_HUNT -> loadProductHunt(page)
    12     else -> when (source) {
    13         is Source.DribbbleSearchSource -> loadDribbbleSearch(source, page)
    14         is Source.DesignerNewsSearchSource -> loadDesignerNewsSearch(source, page)
    15  
    16     }
    17 }

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

    映射处理

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

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

     

    那在Kotlin语言中是怎样的?

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

     

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

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

     

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

    1 pageIndexes += Pair(dataSource, nextPage)

     

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

    1 pageIndexes += dataSource to nextPage

     

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

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

    仅举一例:

     1 private void loadDesignerNewsTopStories(final int page) {
     2     getDesignerNewsApi().getTopStories(page, new Callback<StoriesResponse>() {
     3         @Override
     4         public void success(StoriesResponse storiesResponse, Response response) {
     5             if (storiesResponse != null
     6                     && sourceIsEnabled(SourceManager.SOURCE_DESIGNER_NEWS_POPULAR)) {
     7                 setPage(storiesResponse.stories, page);
     8                 setDataSource(storiesResponse.stories,
     9                         SourceManager.SOURCE_DESIGNER_NEWS_POPULAR);
    10                 onDataLoaded(storiesResponse.stories);
    11             }
    12             loadingCount.decrementAndGet();
    13         }
    14  
    15         @Override
    16         public void failure(RetrofitError error) {
    17             loadingCount.decrementAndGet();
    18         }
    19     });
    20 }

     

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

     1 private inline fun <T> callback(page: Int, sourceKey: String, 
     2             crossinline extract: (T) -> List<PlaidItem>)
     3         = retrofitCallback<T> { result, response ->
     4     if (sourceIsEnabled(sourceKey)) {
     5         val items = extract(result)
     6         setPage(items, page)
     7         setDataSource(items, sourceKey)
     8         onDataLoaded(items)
     9     }
    10 }

     

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

     1 private inline fun <T> retrofitCallback(crossinline code: (T, Response) -> Unit): Callback<T> 
     2         = object : Callback<T> {
     3     override fun success(t: T?, response: Response) {
     4         t?.let { code(it, response) }
     5         loadingCount.decrementAndGet()
     6     }
     7  
     8     override fun failure(error: RetrofitError) {
     9         loadingCount.decrementAndGet()
    10     }
    11 }

     

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

     

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

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

     

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

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

     

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

    1 private inline fun dribbbleLogged(code: () -> Unit) {
    2     if (dribbblePrefs.isLoggedIn) {
    3         code()
    4     } else {
    5         loadingCount.decrementAndGet()
    6     }
    7 }

    总结

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

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

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    android 监听ListView中的 item 和button
    android 获取当前系统及应用信息(二)
    MotionEvent中getX()和getRawX()的区别
    HITS 算法(Hypertext Induced Topic Selection)
    放之四海皆适用的设计原则(二)
    源自神话的写作要义之英雄之旅
    这就是搜索引擎:核心技术详解
    源自神话的写作要义之英雄
    使用Spinner和setDropDownViewResource
    友好界面menu
  • 原文地址:https://www.cnblogs.com/figozhg/p/5058342.html
Copyright © 2020-2023  润新知