Extension des fonctions et des propriétés dans Kotlin

Partager

Parfois nous avons besoin d’ajouter des fonctionnalité à une classe existante et nous sommes souvent obligé de créer une classe dérivée ou utiliser un pattern.

Mais grâce au concept d’extension dans kotlin, vous pouvez ajouter des fonctionnalités à une classe sans créer une classe dérivée ou utiliser un design pattern tel que le pattern décorateur.

Vous pouvez par exemple facilement ajouter une méthode à une classe d’une librairie externe que vous ne pouvez pas modifié sans avoir à modifier le code de base de la librairie.

On appelle cela extension de fonction.Vous pourrez ensuite utiliser cette méthode pour votre propre besoin dans votre projet.

Table des matières

Extension de fonction

Pour déclarer une extension de fonction , vous devez préfixer son nom avec le type receveur ou avec le nom de la classe que vous souhaitez ajouter une fonctionnalité.

L’exemple suivant vous montre comment ajouter une fonction d’extension supprimer() dans la classe Client

fun main(){
  fun Client.supprimer (){
   println("Suppression d'un client")

  }
 val client = Client("VIGAN","Noé")
 client.ajout()
 client.supprimer()

}

class Client(val nom:String,val prenom: String){

  fun ajout(){
   println("Ajout client")
  }

}

Résultat:
Ajout client
Suppression d’un client

Dans cet exemple, la fonction d’extension supprimer() est ajouté à la classe Client en utilisant la notation pointée Client.supprimer.

Ensuite, une instance de la classe Client est créé et les fonctions ajout() et supprimer() sont appelées sur l’objet client.

Ajouter des fonctionnalités aux classes de librairie en utilisant les fonctions d’extension

Il existe plusieurs fonctions d’extension dans les librairies standard de Kotlin.

Kotlin vous permet aussi d’ajouter des fonctionnalités aux classes de librairie standard sans les dérivée grâce aux fonctions d’extension.

Voici un exemple qui vous montre comment ajouter un fonction d’extension aux librairies standard de Kotlin

fun main(){
  fun MutableList<String>.shift(){
        if(!this.isEmpty()){
            this.removeAt(0)
        }
        else throw NoSuchElementException("Collection vide")
    }
 val listName: MutableList<String> = mutableListOf("Noé","Jean","Carole","Rose","Luc")

 println(listName)
 listName.shift()
 println(listName)

}

Résultat:
[Noé, Jean, Carole, Rose, Luc]
[Jean, Carole, Rose, Luc]

Dans cet exemple nous ajoutons la fonction d’extension shift() à la classe MutableList. La fonction shift() supprime le premier élément de la collection.

Vous devez constater aussi que cette fonction s’applique uniquement sur les chaines de caractère.

Notez que le mot clé this fait référence à l’objet récepteur(Celui sur lequel on applique la fonction d’extension shift().)

Les fonctions d’extension et généricité

Dans l’exemple précédent, la fonction d’extension shift() s’applique seulement aux collections de chaîne de caractère.Grace à la généricité, vous pouvez permettre à ce que cette fonction soit générique, et qu’elle s’applique à tous les types dans votre application comme suit.

fun main(){
  fun <T> MutableList<T>.shift(){
        if(!this.isEmpty()){
            this.removeAt(0)
        }
        else throw NoSuchElementException("Collection vide")
    }
 val listName: MutableList<String> = mutableListOf("Noé","Jean","Carole","Rose","Luc")
 val listAge: MutableList<Int> = mutableListOf(12,15,22,21,16)
 println(listName)
 println(listAge)
 listName.shift()
 listAge.shift()
 println(listName)
 println(listAge)


}

Résultat:
[Noé, Jean, Carole, Rose, Luc]
[12, 15, 22, 21, 16]
[Jean, Carole, Rose, Luc]
[15, 22, 21, 16]

Dans cet exemple nous avons déclaré la fonction d’extension shift() comme étant générique puis nous l’avons appelé sur une collection de chaîne de caractère et une collection d’entier.

Les extensions sont résolues statiquement

Les extensions ne modifie pas réellement les classes qu’elles étendent. En définissant une extension, vous n’insérez pas de nouveau membre dans une classe mais vous rendez simplement les nouvelles fonctions appelable avec la notation pointée sur les variables de ce type.

La fonction d’extension appelé dépend du type d’expression sur laquelle elle est appelée et non le type du résultat de l’évaluation de cette expression au moment de l’exécution. Voir l’exemple suivant.

fun main(){
    open class Animal
    class Chien: Animal()
    fun Animal.getName() = "Animal"
    fun Chien.getName() ="Chien"
    fun afficherNom(a:Animal){
        println(a.getName())
    }

    afficherNom(Chien())
}

Résultat: Animal

Dans cet exemple la console affiche Animal parce que la fonction d’extension appelée dépend du type déclaré pour le paramètre a de la fonction afficherNom qui est la classe Animal.

D’abord la classe Chien dérive de la classe Animal, l’argument passé à la fonction afficherNom est une instance de la classe Chien et puisque avec l’héritage des classes, l’appelle d’une fonction redéfinie dans une classe dérivée est déterminé dynamiquement au moment de l’exécution du programme,c’est normalement la fonction getName() de la classe Chien qui devrait être appelé mais ici c’est la fonction d’extension getName() de la classe Animal qui est appelé

Donc on constate que le type d’une fonction d’extension est déterminé statiquement au moment de la compilation contrairement à une méthode définir dans une classe dérivée dont son appelle est déterminé dynamiquement au moment de l’exécution.

Fonction d’extension et fonction membre

Si une classe à une fonction membre et une fonction d’extension ayant le même type récepteur ,le même nom que la fonction membre et pouvant s’appliquée à des arguments données est définies, c’est la fonction membre qui est appelée.

fun main(){
     class Animal{
        fun getName() ="Animal"
    }

    fun Animal.getName() ="Fonction d'extension"

   print(Animal().getName())

}

Résultat: Animal

Vous devez constater que la c’est la fonction getName() déclaré dans la classe Animal qui est appelée.

Cependant la fonction d’extension sera appelée lorsqu’elle surcharge une fonction membre portant le même nom mais avec une signature différente.

fun main(){
     class Nombre(val a: Int){
        fun somme(b: Int): Int{
         return a+ b
        }
    }

    fun Nombre.somme( a: Int,b: Int, c: Int ):Int{
      return a + b + c
    }
  print(Nombre(1).somme(10,5,2)  )

}

Résultat: 17

Récepteur nullable

Les fonctions d’extension peuvent être définies avec un type de classe nullable.C’est à dire de telles extensions peuvent être appelées sur une variable d’objet même si sa valeur est nul. La fonction d’extension pourra vérifier si l’objet courant this==null (est égal à null) dans son corps.

fun main(){
 fun Personne?.afficher(){
     if(this== null){
         println("Personne inexistante")
     }
     else{
        println(this.toString())
     }
 }

    Personne("VIGAN").afficher()
    null.afficher()

}

data class Personne(val nom: String){


}

Résultat:
Personne(nom=VIGAN)
Personne inexistante

Les propriétés d’extension

Comme les fonctions d’extension, Kotlin supporte aussi les propriétés d’extension.

fun main(){
 val calcul = Calcul(10,5)
  println(calcul.somme)

}

val Calcul.somme: Int  get() =this.a + this.b

data class Calcul (val a: Int, val b: Int)

Résultat: 15

Notez que, puisque les extension n’insère pas réellement de membre dans les classes, il n’existe pas de moyen pour un propriété d’extension d’avoir un champ auto généré(backing field ).

C’est pourquoi vous ne pouvez pas initialiser les propriétés d’extension.Leur contenues peut uniquement être définies par des getter et setter.

Les extensions d’objets compagnons

Vous pouvez aussi définir des propriétés et des fonctions d’extension avec les objets compagnons.Tout comme pour les membres d’un objet compagnons les extensions peuvent être appelées en utilisant le nom de la classe de l’objet compagnon.

fun main(){
 Calcul.affiche()
}

class Calcul(){
  companion object{

  }
}
fun Calcul.Companion.affiche(){
   print("fonction d'extension d'un objet compagnon")
}

Porté des extensions

La plupart du temps, nous définissons les d’extensions au niveau supérieur,c’est à dire sous un package

package com.exemple.principale

class Calcul{

}
fun Calcul.addition(){

}

Pour utiliser cette extension en dehors de son package,vous devez l’importer

package com.exemple.teste

import com.exemple.principale.Calcul
import com.exemple.principale.addition

fun main(){
    val calcul = Calcul()
    calcul.addition()
}

Vous devez constater que la classe Calcul et la fonction extension addition() ont été importé avant d’être utilisé.

Déclaration d’extension comme membre

Dans une classe, vous pouvez déclarer des extensions pour une autre classe. Dans une telle extension, il y a plusieurs récepteurs implicites( des objets dont les membres peuvent être accédés sans qualificatif).

L’instance de la classe dans laquelle l’extension est déclarée est appelée receveur dispatch, et l’instance du type récepteur de la méthode d’extension est appelée receveur d’extension.

fun main(){
    val dataBase = DataBase(DbConnect(), Config())
    dataBase.connectToDataBase()

}


class DbConnect(){
    lateinit var config: Config
    fun init(){
        print("Initialisation")
    }

}
class Config{

}
class  DataBase(val dbConnect: DbConnect,val config: Config){

    fun getConfig(){
        dbConnect.config = config
    }
    fun DbConnect.connect(){
        getConfig()
        init()
    }

    fun connectToDataBase(){
        dbConnect.connect()
    }

}

Vous devez constater que les fonctions membres getConfig() et init() sont appelées sans faire référence à leur récepteur qui sont implicite et déterminer par Kotlin.

En cas de conflit de nom entre les membres du récepteur de destination et le récepteur d’extension,le récepteur d’extension à la priorité.Pour faire référence au récepteur d’expédition,vous pouvez utiliser le mot clé this.

class DbConnect(){
    lateinit var config: Config
    fun init(){
        print("Initialisation")
    }

}
class Config{

}
class  DataBase(val dbConnect: DbConnect,val config: Config){

    fun init(){
        dbConnect.config = config
    }
    fun DbConnect.connect(){
        this@DataBase.init()//Utilisation de this.
        init()
    }



}

Les extensions déclarées en tant que membres peuvent être déclarées open et remplacées dans des sous-classes.

Cela signifie que le destinataire de telles fonctions est virtuel ou dynamique en ce qui concerne le type du récepteur de destination, mais statique en ce qui concerne le type récepteur d’extension.

fun main(){
  PersonneCaller().call(Personne())
  EtudiantCaller().call(Personne())
  EtudiantCaller().call(Etudiant())
}


open class  Personne

class Etudiant: Personne(){}
open class PersonneCaller{
     open fun Personne.affiche(){
         println("Personne fonction d'extension affiche dans PersonneCaller")
     }
     open fun Etudiant.affiche(){
         println("Etudiant fonction d'extension affiche dans PersonneCaller")
     }

    fun call(personne: Personne){
        personne.affiche()
    }
}
class EtudiantCaller: PersonneCaller(){

    override fun Personne.affiche() {
        println("Personne fonction d'extension affiche dans EtudiantCaller")
    }
    override fun Etudiant.affiche() {
        println("Etudiant fonction d'extension affiche dans EtudiantCaller")
    }
}

Comme vous pouvez le constater même quand vous appelez la méthode call() de la manière suivante EtudiantCaller().call(Etudiant()), c’est toujours la méthode affiche() du récepteur d’extension de la classe Personne qui est appelée.

Par contre puisque la méthode affiche dans le récepteur de destination est redéfini dans le récepteur de destination dérivée EtudiantCaller alors lorsque dans la méthode call() on appelle la méthode affiche() comme suit personne.affiche(), la méthode affiche() défini dans les récepteur de destination a appelé est déterminer dynamiquement lors de l’exécution.

Conclusion

Voila, nous sommes à la fin de ce tutoriel sur les extensions dans kotlin. J’espère que ce tutoriel vous aidera.A bientôt pour un nouveau tutoriel.

Autres ressources

https://kotlinlang.org/docs/reference/extensions.html#extension-properties


Partager

Laisser un commentaire

Résoudre : *
36 ⁄ 12 =


%d