Key Takeaways(劃重點(diǎn))
- 設(shè)計(jì)原則不要忘
- 擴(kuò)展是個(gè)補(bǔ)鍋匠
- 定向輸出是好手
- 升級(jí)外掛要當(dāng)心
這里假定你對(duì)Kotlin的Extension Functioins(擴(kuò)展函數(shù))有一定了解娃殖,如果沒有箩退,請(qǐng)先瀏覽官網(wǎng)有關(guān)它的定義和常見用法裙戏。
面向?qū)ο缶幊?/h2>
在Kotlin的官方網(wǎng)站介紹擴(kuò)展函數(shù)的時(shí)候精续,使用的例子是Collections.swap(List, int, int)
翰舌,那我們也拿它來說事吧。
這個(gè)是swap在Java中的定義:
Collections.java
public class Collections {
/** @since 1.4 */
public static void swap(List<?> list, int i, int j)
}
看到這個(gè)吨凑,我就琢磨一個(gè)事贪婉,為什么我不能直接把這個(gè)方法定義在List上呢,好比這樣:
List.java
public interface List<E> extends Collection<E> {
void swap(int i, int j)
}
如果這樣可行咒钟,哪里還要Collections.swap啊吹由。
但現(xiàn)實(shí)是這個(gè)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+編譯報(bào)錯(cuò)
}
}
所以朱嘴,之前我們認(rèn)為的別扭設(shè)計(jì)倾鲫,不是Java的設(shè)計(jì)師沒想到,而真是時(shí)機(jī)未到萍嬉。
那如果我們?cè)贙otlin中也來這么玩一下(當(dāng)然是用擴(kuò)展函數(shù)而不是工具類了):
fun MutableList<E>.swap(i: int, j: int): Unit
從使用的角度乌昔,和原生函數(shù)是一樣的:mutableListOf(1, 2, 3).swap(0, 2)
。但這個(gè)時(shí)候我們就需要問一句壤追,為什么不直接使用原生的實(shí)現(xiàn)呢磕道?
所以,基于設(shè)計(jì)原則的原生函數(shù)是始終優(yōu)先于擴(kuò)展函數(shù)的行冰。(千萬別忘了OO的多態(tài)的強(qiáng)大溺蕉,而擴(kuò)展函數(shù)恰恰由于是靜態(tài)調(diào)用,不支持多態(tài)的)
擴(kuò)展函數(shù)是個(gè)補(bǔ)鍋匠
Kotlin自身就有很多擴(kuò)展函數(shù)悼做,譬如Array<T>.map(transform: (T) -> R): List<R>
疯特。這個(gè)擴(kuò)展可以讓數(shù)組按需轉(zhuǎn)換,也是一個(gè)高頻使用的擴(kuò)展函數(shù)贿堰。
public inline fun <T, R> Array<out T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(size), transform)
}
看到這里可能就有疑問辙芍,為什么不做成Array的原生函數(shù)呢?
其實(shí)羹与,如果閱讀代碼夠仔細(xì)故硅,可以看到,接口定義和實(shí)現(xiàn)中分別出現(xiàn)了List<R>
纵搁、ArrayList<R>
吃衅,這些代表著依賴。Array本身其實(shí)是不應(yīng)該依賴他們的腾誉,如果把這個(gè)map功能進(jìn)行原生實(shí)現(xiàn)徘层,就存在接口污染問題(高內(nèi)聚,低耦合設(shè)計(jì)原則)利职。
所以這個(gè)時(shí)候趣效,擴(kuò)展函數(shù)就可以很好的補(bǔ)上這個(gè)鍋 了。
特定場(chǎng)景的定向擴(kuò)展
談到原生函數(shù)猪贪,一般而言只會(huì)存放普遍需要的跷敬。對(duì)于只適用于某些特定場(chǎng)合/環(huán)境/上下文的,就不適合了热押。譬如Domain Object或OOA的Entity西傀,如Person,在某些場(chǎng)景需要輸出成json的時(shí)候桶癣,如果可以直接person.toJson()會(huì)很便捷拥褂。但這個(gè)不是基礎(chǔ)需求,也不希望引入JSON帶來第三方類/庫(kù)牙寞。另外這個(gè)口子開了饺鹃,后面其他使用者需要輸出成xml的時(shí)候,同樣會(huì)再次引入xml而帶來更多的第三方類/庫(kù)间雀。
這樣原本的一個(gè)存粹的Domain Object或Entity或者說我們的common/core module尤慰,依賴了一堆堆第三方包,咋看都怪怪的雷蹂。
這個(gè)時(shí)候伟端,擴(kuò)展函數(shù)又是一個(gè)很好的輸出。在需要的應(yīng)用中匪煌,我們可以自行擴(kuò)展:
// app: JSON app
fun Person.toJson(): String
// app: XML app
fun Person.toXml(): String
原生函數(shù)執(zhí)行優(yōu)先級(jí)高于擴(kuò)展函數(shù)
對(duì)第三方庫(kù)進(jìn)行擴(kuò)展的時(shí)候责蝠,需要注意擴(kuò)展函數(shù)和原生函數(shù)在執(zhí)行上的一個(gè)先后順序:原生函數(shù)優(yōu)先于擴(kuò)展函數(shù)。
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
這個(gè)問題在升級(jí)被擴(kuò)展的類庫(kù)的時(shí)候尤其需要注意萎庭。拿上面例子中的toJson來說霜医,本來原生沒提供,所以我們擴(kuò)展了驳规。但對(duì)方可能也意識(shí)到這個(gè)蠻有用(甭糾結(jié)上面不是說違反原則嗎)肴敛,也就在某次版本中添加了這個(gè)功能。問題是對(duì)方的原生實(shí)現(xiàn)在null值處理的時(shí)候和你不一樣(假如對(duì)方null的時(shí)候String會(huì)是空串 "name": "",而你是不輸出)医男,就可能導(dǎo)致升級(jí)之后由于原生函數(shù)優(yōu)先執(zhí)行砸狞,你的程序可能就會(huì)掛了。
所以镀梭,玩外掛每次升級(jí)都需要額外注意刀森。
總結(jié)下擴(kuò)展函數(shù)的使用要點(diǎn)/場(chǎng)景:
- 設(shè)計(jì)原則不要忘
- 擴(kuò)展是個(gè)補(bǔ)鍋匠
- 定向輸出是好手
- 升級(jí)外掛要當(dāng)心
希望這篇博文能對(duì)你有所幫助,喜歡的話點(diǎn)個(gè)贊吧??报账!
更多Kotlin的實(shí)用技巧研底,請(qǐng)參考《Kotlin邊用邊學(xué)系列》