當我們為類定義擴展函數時,它不會作為成員添加到類中。擴展函數是一種特殊的函數,它默認的第一個參數是函數的接受者,如下例所示,擴展函數被編譯成普通函數。
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
避免,而不是禁止
這條規則並不適用於任何地方,最明顯的情況是,當我們定義 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, 因為成員擴展函數存在很多缺點,我們應該儘量避免,這只是建議,不是強制,更不應該使用成員擴展函數來限制可見性,你應該使用可見修飾符,限制擴展函數的可見性。

推薦文章
網友提議為 Kotlin 引入這些新特性 ...
一道 Kotlin 面試題,據說答對的人不多
Kotlin 1.6 正式發布,帶來這些新特性


