Kotlin邊用邊學(xué):別把Extension Function玩壞了

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é)系列

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市透罢,隨后出現(xiàn)的幾起案子榜晦,更是在濱河造成了極大的恐慌,老刑警劉巖羽圃,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乾胶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡统屈,警方通過查閱死者的電腦和手機(jī)胚吁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來愁憔,“玉大人腕扶,你說我怎么就攤上這事《终疲” “怎么了半抱?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)膜宋。 經(jīng)常有香客問我窿侈,道長(zhǎng),這世上最難降的妖魔是什么秋茫? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任史简,我火速辦了婚禮,結(jié)果婚禮上肛著,老公的妹妹穿的比我還像新娘圆兵。我一直安慰自己,他們只是感情好枢贿,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布殉农。 她就那樣靜靜地躺著,像睡著了一般局荚。 火紅的嫁衣襯著肌膚如雪超凳。 梳的紋絲不亂的頭發(fā)上愈污,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音轮傍,去河邊找鬼暂雹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛金麸,可吹牛的內(nèi)容都是我干的擎析。 我是一名探鬼主播簿盅,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼挥下,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了桨醋?” 一聲冷哼從身側(cè)響起棚瘟,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喜最,沒想到半個(gè)月后偎蘸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瞬内,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年迷雪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虫蝶。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡章咧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出能真,到底是詐尸還是另有隱情赁严,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布粉铐,位于F島的核電站疼约,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蝙泼。R本人自食惡果不足惜程剥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汤踏。 院中可真熱鬧织鲸,春花似錦、人聲如沸茎活。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽载荔。三九已至盾饮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丘损。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工普办, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人徘钥。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓衔蹲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親呈础。 傳聞我的和親對(duì)象是個(gè)殘疾皇子舆驶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容