美文网首页我爱编程半栈工程师Kotlin编程
Kotlin边用边学:别把Extension Function玩

Kotlin边用边学:别把Extension Function玩

作者: 朱和 | 来源:发表于2018-07-27 21:39 被阅读11次

    Key Takeaways(划重点)

    • 设计原则不要忘
    • 扩展是个补锅匠
    • 定向输出是好手
    • 升级外挂要当心

    这里假定你对Kotlin的Extension Functioins(扩展函数)有一定了解,如果没有,请先浏览官网有关它的定义和常见用法。

    面向对象编程

    在Kotlin的官方网站介绍扩展函数的时候,使用的例子是Collections.swap(List, int, int),那我们也拿它来说事吧。

    这个是swap在Java中的定义:

    Collections.java

    public class Collections {
        /** @since 1.4 */
        public static void swap(List<?> list, int i, int j)
    }
    

    看到这个,我就琢磨一个事,为什么我不能直接把这个方法定义在List上呢,好比这样:

    List.java

    public interface List<E> extends Collection<E> {
        void swap(int i, int j)
    }
    

    如果这样可行,哪里还要Collections.swap啊。

    但现实是这个Collections.swap()是从Java 1.4就有的,而想要List能够原生支持swap得等到Java 8的Interface Default Methods

    public interface List<E> extends Collection<E> {
        void swap(int i, int j) {
            set(i, l.set(j, get(i))); // 不是Java 8+编译报错
        }
    }
    

    所以,之前我们认为的别扭设计,不是Java的设计师没想到,而真是时机未到。

    那如果我们在Kotlin中也来这么玩一下(当然是用扩展函数而不是工具类了):

    fun MutableList<E>.swap(i: int, j: int): Unit
    

    从使用的角度,和原生函数是一样的:mutableListOf(1, 2, 3).swap(0, 2)。但这个时候我们就需要问一句,为什么不直接使用原生的实现呢?

    所以,基于设计原则的原生函数是始终优先于扩展函数的。(千万别忘了OO的多态的强大,而扩展函数恰恰由于是静态调用,不支持多态的)

    扩展函数是个补锅匠

    Kotlin自身就有很多扩展函数,譬如Array<T>.map(transform: (T) -> R): List<R>。这个扩展可以让数组按需转换,也是一个高频使用的扩展函数。

    public inline fun <T, R> Array<out T>.map(transform: (T) -> R): List<R> {
        return mapTo(ArrayList<R>(size), transform)
    }
    

    看到这里可能就有疑问,为什么不做成Array的原生函数呢?

    其实,如果阅读代码够仔细,可以看到,接口定义和实现中分别出现了List<R>ArrayList<R>,这些代表着依赖。Array本身其实是不应该依赖他们的,如果把这个map功能进行原生实现,就存在接口污染问题(高内聚低耦合设计原则)。

    所以这个时候,扩展函数就可以很好的补上这个锅 了。

    特定场景的定向扩展

    谈到原生函数,一般而言只会存放普遍需要的。对于只适用于某些特定场合/环境/上下文的,就不适合了。譬如Domain Object或OOA的Entity,如Person,在某些场景需要输出成json的时候,如果可以直接person.toJson()会很便捷。但这个不是基础需求,也不希望引入JSON带来第三方类/库。另外这个口子开了,后面其他使用者需要输出成xml的时候,同样会再次引入xml而带来更多的第三方类/库。

    这样原本的一个存粹的Domain Object或Entity或者说我们的common/core module,依赖了一堆堆第三方包,咋看都怪怪的。

    这个时候,扩展函数又是一个很好的输出。在需要的应用中,我们可以自行扩展:

    // app: JSON app
    fun Person.toJson(): String
    
    // app: XML app
    fun Person.toXml(): String
    

    原生函数执行优先级高于扩展函数

    对第三方库进行扩展的时候,需要注意扩展函数和原生函数在执行上的一个先后顺序:原生函数优先于扩展函数。

    If a class has a member function, and an extension function is defined which has the same receiver type, the same name and is applicable to given arguments, the member always wins

    这个问题在升级被扩展的类库的时候尤其需要注意。拿上面例子中的toJson来说,本来原生没提供,所以我们扩展了。但对方可能也意识到这个蛮有用(甭纠结上面不是说违反原则吗),也就在某次版本中添加了这个功能。问题是对方的原生实现在null值处理的时候和你不一样(假如对方null的时候String会是空串 "name": "",而你是不输出),就可能导致升级之后由于原生函数优先执行,你的程序可能就会挂了。

    所以,玩外挂每次升级都需要额外注意

    总结下扩展函数的使用要点/场景:

    • 设计原则不要忘
    • 扩展是个补锅匠
    • 定向输出是好手
    • 升级外挂要当心

    希望这篇博文能对你有所帮助,喜欢的话点个赞吧👍!

    更多Kotlin的实用技巧,请参考《Kotlin边用边学系列

    参考资料

    相关文章

      网友评论

        本文标题:Kotlin边用边学:别把Extension Function玩

        本文链接:https://www.haomeiwen.com/subject/lofnmftx.html