close

當我們為類定義擴展函數時,它不會作為成員添加到類中。擴展函數是一種特殊的函數,它默認的第一個參數是函數的接受者,如下例所示,擴展函數被編譯成普通函數。

funString.isPhoneNumber():Boolean=length==7&&all{it.isDigit()}

編譯成一個類似於擴展函數的函數

funisPhoneNumber(`$this$:String):Boolean=$this$.length==7&&$this`.all{it.isDigit()}

除了為類定義擴展函數之外,還可以定義成員擴展,甚至還可以在接口中定義擴展。

interfacePhoneBook{funString.isPhoneNumber():Boolean}classFizz:PhoneBook{overridefunString.isPhoneNumber():Boolean=this.length==7&&this.all{it.isDigit()}}

不要僅僅為了限制可見性,而將函數定義為成員擴展函數,如下所示。

//Badpractice,donotdothisclassPhoneBookIncorrect{funverify(number:String):Boolean{require(number.isPhoneNumber())//...}//...funString.isPhoneNumber():Boolean=this.length==7&&this.all{it.isDigit()}}

其實這樣做並沒有真正限制可見性,它只會使擴展函數變得更加複雜,調用的時候,需要同時提供擴展接受者和調度接受者。

PhoneBookIncorrect().apply{"1234567890".isPhoneNumber()}

你應該使用可見修飾符,限制擴展函數的可見性,而不是將其設置成員擴展函數。

classPhoneBook{funverify(number:String):Boolean{require(number.isPhoneNumber())//...}//...}//ThisishowwelimitextensionfunctionsvisibilityprivatefunString.isPhoneNumber():Boolean=this.length==7&&this.all{it.isDigit()}

如果你需要將一個函數作為成員,並且希望像調用擴展函數一樣使用它,請考慮使用let。

classPhoneBook(privatevalphoneNumberVerifier:PhoneNumberVerifier){funverify(number:String):Boolean{require(number.let(::isPhoneNumber))}privatefunisPhoneNumber(number:String):Boolean=phoneNumberVerifier.verify(number)}

為什麼需要避免成員擴展函數

建議儘量避免使用成員擴展函數,主要有以下幾個原因:

不支持引用

valref=String::isPhoneNumbervalstr="1234567890"valboundedRef=str::isPhoneNumbervalrefX=PhoneBookIncorrect::isPhoneNumber//ERRORvalbook=PhoneBookIncorrect()valboundedRefX=book::isPhoneNumber//ERROR

兩個接受者隱式的訪問可能會令人困惑
classA{vala=10}classB{vala=20valb=30funA.test()=a+b//Isit40or50?}
當我們期望修改引用接受者的時候,我們不清楚是修改的是擴展接受者還是調度接受者
classA{//...}classB{//...funA.update()...//DoesitupdateAorB?}
對於經驗較少的開發人員來說,看到成員擴展可能是違反直覺,可讀性很差。

避免,而不是禁止

這條規則並不適用於任何地方,最明顯的情況是,當我們定義 DSL 時,需要使用成員擴展。

DSL 全稱 Domain Specific Language 即 領域特定語言,在 Kotlin 中最主要的實現方式是高階函數

當需要調用在某個作用域上定義的函數時,成員擴展也是非常有用的,舉兩個例子,一個例子可能是使用 produce 生成 Channel 的成員函數,另一個是定義在接口中的,集成測試函數。

classOrderUseCase(//...){//...privatefunCoroutineScope.produceOrders()=produce<Order>{varpage=0do{valorders=api.requestOrders(page=page++).orEmpty()for(orderinorders)send(order)}while(orders.isNotEmpty())}}interfaceUserApiTrait{funTestApplicationEngine.requestRegisterUser(token:String,request:RegisterUserRequest):UserJson?=...funTestApplicationEngine.requestGetUserSelf(token:String):UserJson?=...//...}

我們建議儘可能避免定義成員擴展函數,但是如果它們是最好的選擇,我們還是會使用它們。

總結

本篇文章主要介紹了,應當儘量避免使用成員擴展函數,除了特殊的場景例如 DSL, 因為成員擴展函數存在很多缺點,我們應該儘量避免,這只是建議,不是強制,更不應該使用成員擴展函數來限制可見性,你應該使用可見修飾符,限制擴展函數的可見性。

END


推薦文章

網友提議為 Kotlin 引入這些新特性 ...

一道 Kotlin 面試題,據說答對的人不多

Kotlin 1.6 正式發布,帶來這些新特性



加好友進群,技術聊不停

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()