Héritage dans kotlin


L’héritage est l’un des concepts les plus importants de la programmation orienté objet. Il permet à une nouvelle classe d’hériter de toutes les fonctionnalités (propriétés et fonctions membres) d’une classe existant.

Ainsi, la classe qui hérite des fonctionnalités est appelée classe dérivée ou classe fille ou sous classe et la classe dont les fonctionnalités sont héritées est appelée classe de base ou classe parent.

Dans ce tutoriel vous allez découvrir comment créer une classe dérivée dans kotlin.

Table des matières

L’héritage

Toutes les classes dans kotlin ont une super classe commune appelée Any. Toutes les classes que vous créez dans kotlin héritent implicitement de la classe Any sans que cela ne soit explicitement déclaré.

Prenons comme exemple la classe Jeu suivante

class Jeu

La classe Jeu hérite implicitement de la classe Any.

Puisque la classe Any possède trois(3) méthodes equals()hashCode() et toString().Ces méthodes sont donc toutes définies dans toutes les classes que vous allez créer.

Vous pouvez par exemple redéfinir ou implémenter ces méthodes dans la classe Jeu et ajouter des fonctionnalité qui seront propre à la classe Jeu.

Classe dérivée

Voici comment créer une classe Chien qui hérité d’une classe de base Animal

open class Animal{

}

class Chien:Animal(){

}

Par défaut,toutes les classes que vous créez dans Kotlin sont final c’est à dire des classes qui ne peuvent pas être héritées.

Pour permettre à une classe d’être héritable vous devez la marquer avec le modificateur open comme cela est fait avec la classe Animal précédente.

Vous devez aussi remarquer que dans l’exemple précédent , la classe dérivée Jeu déclare explicitement son super type Animal en le plaçant dans son entre après les deux points.

Une classe dérivée ne se content pas juste de marquer sont super type, elle doit aussi initialiser la classe de base(son super type).

Si la classe de base ne possède pas de constructeur primaire alors,la classe dérivée doit initialiser la classe de base en appelant le constructeur par défaut de la classe de base comme dans l’exemple précédent ou comme dans l’exemple suivant

open class  Personne{
    
}

class Eleve:Personne(){
    
}

Vous devez constater dans cet exemple que la classe dérivée Eleve initialise la classe de base Animal en appelant le constructeur par défaut de la classe Animal.

Si La classe de base possède un constructeur primaire avec des paramètres et si la classe dérivée aussi possède un constructeur primaire, la classe dérivé doit initialiser la classe de base dans son entête avec les paramètres qui sont passés dans son constructeur primaire.

Donc le constructeur primaire de la classe dérivée doit avoir dans la liste de ses paramètres,les paramètres du constructeur primaire de la classe de base pour pouvoir initialiser la classe de base lors de la création de la classe dérivée.

Voici un exemple qui illustre le cas d’une classe de base et une classe dérivé avec tout deux un constructeur primaire

open class Animal (val name: String){
    
}

class Chien(name: String):Animal(name){

}

Vous devez constater que la classe dérivée Chien initialise le classe de base Animal en appelant le constructeur primaire de la classe de base(Animal) et en passant son paramètre name au constructeur primaire de la classe de base(Animal).

Si la classe dérivée ne possède pas de constructeur primaire et le classe de base possède un constructeur primaire,tous les constructeurs secondaires de la classe dérivée doivent se charger d’initialiser la classe de base avec le mot clé super ou déléguer l’initialisation à tout autre constructeur.

Voici un exemple qui illustre le case d’une classe dérivée sans constructeur primaire et une classe de base avec un constructeur primaire

open class Animal (val name: String){

}

class Chien:Animal{
    
  constructor(name: String):super(name){

  }
    
  /*
  * Ce constructeur délègue l'initialisation à un autre constructeur
  * de ce classe avec le mot clé this
  * */  
  constructor(name: String,race: String ):this(name){
      
  }  
}

Dans cette exemple la classe Dérivée Chien initialise la classe de base Animal à partir de son constructeur secondaire avec le mot clé this.

La classé dérivée peut aussi initialiser la classe de base en appelant un constructeur primaire ou un constructeur secondaire de la classe de base comme dans l’exemple suivant

open class  Personne(val nom: String){
    constructor(name: String,prenom:String):this(name){

    }
}

class Eleve:Personne{
  constructor(nom:String):super(nom){

  }
  constructor(nom: String,prenom: String):super(nom,prenom){
      
  }  
}

Dans cet exemple,la classe dérivée Eleve initialise la classe de base Personne à partir d’un constructeur secondaire de la classe de base Animal avec le mot clé super

Les fonctions membres

Lorsqu’une classe dérive d’une classe de base, elle hérite des fonctions membres de la classe de base.

Toute comme les classes, par défaut les fonctions membres que vous ajoutez dans une classe sont marqué final c’est à dire qu’elles ne peuvent pas être redéfinies dans une classe dérivée.

Tout d’abord pour permettre à une fonction d’être redéfinie(implémentée) dans une classe dérivée, vous devez la marquer open.

Ensuite vous devez marquer la fonction membre de la classe de base avec le modificateur override dans la classe dérivée sinon, le compilateur vous signalera une erreur.

Voici un exemple qui illustre comment redéfinir une fonction membre d’une classe de base dans une classe dérivée.

fun main(){
 val chien=Chien("Bouledogue")
  chien.afficher()
}

open class Animal (val name: String){
  open fun afficher(){
     println("Mon nom d'animal est $name")
    }
}

class Chien(name: String):Animal(name){

    override fun afficher() {
        println("Mon nom de chien est $name")
    }
}

Résultat:Mon nom de chien est Bouledogue

Une fonction membre marquée override dans une classe dérivée peut aussi être redéfini dans une autre classe dérivé.si vous souhaitez empêcher cette fonction d’être redéfinie dans une autre classe, vous devez la marquer final. Voir l’exemple suivant.

open class Animal (val name: String){
  open fun afficher(){
     println("Mon nom d'animal est $name")
    }
   
}

class Chien(name: String):Animal(name){

    final override fun afficher() {
        println("Mon nom de chien est $name")
    }
}

Dans cet exemple, la fonction membre afficher est marquée final pour empêcher qu’elle soit redéfinie dans une autre classe dérivée.

Les propriétés redéfinies

La redéfinition d’une propriété dans une classe dérivée fonctionne comme la redéfinition de fonction membre dans une classe dérivée.C’est à dire la propriété à redéfinir doit être marquée open dans la classe de base et lorsqu’elle est déclarée à nouveau ,dans la classe dérivé, elle doit être marquée override . Voir l’exemple suivant

open class Animal (val name: String){
    open var couleur: String = "Noir"
    open fun afficher(){
        println("Mon nom d'animal est $name")
    }

}

class Chien(name: String):Animal(name){
    override var couleur: String = "Bringé"

    override fun afficher() {
        println("Mon nom de chien est $name")
    }
}

Dans cet exemple, la propriété couleur est marquée open dans la classe de base et marquée override dans la classe dérivée.

Redéfinition d’une propriété val en var

Vous pouvez aussi redéfinir une propriété immuable( propriété marquée val) en une propriété mutable (propriété marquée var).

Cela est possible parce qu’une propriété val déclare un getter.En définissant à nouveau dans une classe dérivé une propriété étant immuable en une propriété mutable(propriété marqué var), vous pourrai ajouter en plus un setter dans la classe dérivée.

Donc puisque c’est un ajout de setter ,et non une suppression du getter alors ça fonctionne.

Voici un exemple qui illustre la redéfinition dune propriété val en var

open class Animal (val name: String){
    open val couleur: String = "Noir"
    open fun afficher(){
        println("Mon nom d'animal est $name")
    }

}

class Chien(name: String):Animal(name){
    override var couleur: String = "Bringé"

    override fun afficher() {
        println("Mon nom de chien est $name")
    }
}

Dans cet exemple la propriété couleur est immuable dans la classe de base (propriété marquée val) alors qu’elle est mutable dans la classe dérivée.

Si vous créez une instance de la classe de base(Animal) vous ne pouvez pas modifier la couleur défini comme suit.

fun main(){
  val animal=Animal("Chien")
    animal.couleur="Merle" //Erreur du compilateur
}

Dans la classe Animal, la propriété couleur est immuable(propriété marquée val)

Tandis que pour la classe dérivée de la classe animal (Chien) , vous pouvez modifier la valeur comme suit.

fun main(){
  val animal=Chien("Chien")
    animal.couleur="Merle" //Erreur du compilateur
}

Dans la classe Chien , la propriété couleur est mutable(propriété marquée var)

Redéfinition des getters et setters

Vous pouvez redéfinir une propriété d’une classe de base dans une classe dérivée en initialisant à nouveau la propriété ou en définissant des getters et setters personnalisés .Voir l’exemple suivant

open class Animal (val name: String){
    open var couleur: String = "Noir"
    open fun afficher(){
        println("Mon nom d'animal est $name")
    }

}

class Chien(name: String):Animal(name){
    override var couleur: String = "Bringé"
    get()=field.capitalize()
    set(value) {
        field = value.capitalize()
    }

    override fun afficher() {
        println("Mon nom de chien est $name")
    }
}

Dans cet exemple les accesseurs personnalisés de la propriété couleur sont définis dans la classe dérivée.

L’ordre d’initialisation des classes dérivées

Lors de la construction d’une instance de classe dérivée, l’initialisation de la classe de base s’effectue avant l’initialisation de la classe dérivé et uniquement précédée de l’évaluation des arguments du constructeur de la classe de base.

fun main(){
  val chien=Chien("Chien")
}

open class Animal (val name: String){
    init {
        println("Initialisation de la classe de base")
    }


}

class Chien(name: String):Animal(name.capitalize().also { println("Evaluation $it") }){
    init {
        println("Initialisation de la vérivé")
    }

}

Résultat:
Evaluation Chien
Initialisation de la classe de base
Initialisation de la vérivé

On constate bien que l’évaluation des arguments du constructeur de la classe de base s’effectue avant l’initialisation de la classe de base qui elle s’effectue encore avant l’initialisation de la classe dérivée

Appeler la classe de base avec le mot clé super

le code écrit dans la classe dérivée peut appeler une fonction membre ou les accesseurs d’une propriété de la classe de base.

open class Animal (val name: String){

    open var couleur: String = "Noir"
    open fun afficher(){
        println("Mon nom d'animal est $name")
    }

}

class Chien(name: String):Animal(name.capitalize().also { println("Evaluation $it") }){

    override var couleur: String = super.couleur

    override fun afficher() {
       super.afficher()
       println("Je suis un chien")
    }
}

la fonction membre affiche et le getter de la propriété couleur sont appelés dans la classe dérivée avec le mot clé super.

L’ordre de redéfinition

Lorsqu’une classe dérivée hérite de plusieurs classes de base ayant une même fonction membre ,la classe dérivée doit redéfinir cette fonction membre et fournir sa propre implémentation ou appeler directement une implémentation d’une des classes de base.

Pour appeler une implémentation d’une des classes de base depuis la classe dérivée, on utilise super,suivi du nom de la classe entre des chevrons et enfin le nom de la fonction membre.

Voir l’exemple

open class Animal (val name: String){

    open fun afficher(){
        println("Mon nom d'animal est $name")
    }

}
interface Action{
   fun afficher()
}

class Chien(name: String):Animal(name.capitalize().also { println("Evaluation $it") }),Action{
    
    override fun afficher() {
       super<Animal>.afficher()
       println("Je suis un chien")
    }
}

Dans cet exemple,vous constater bien que la fonction afficher de la classe Animal et de l’interface Action est redéfinie dans la classe Chien puis elle appelle explicitement la fonction afficher de la classe Animal.

Conclusion

Voila,nous sommes à la fin de ce tutoriel dans lequel vous avez appris l’héritage des classes dans Kotlin. J’espère que ce tutoriel vous aidera.A bientôt pour un nouveau tutoriel.

Autres ressources

https://kotlinlang.org/docs/reference/classes.html


Laisser un commentaire

Résoudre : *
2 + 18 =


%d